Extracted out crypto classes. Added PGP handling

This commit is contained in:
nathan 2015-11-22 21:37:55 +01:00
parent 7388a8efb7
commit 88ccf24895
8 changed files with 1187 additions and 255 deletions

View File

@ -1,3 +1,18 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>

View File

@ -16,11 +16,14 @@
package dorkbox.util.crypto;
import com.twmacinta.util.MD5;
import dorkbox.util.OS;
import dorkbox.util.bytes.LittleEndian;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -57,6 +60,15 @@ import java.util.jar.JarFile;
public final
class Crypto {
private
Crypto() {
}
// CUSTOM_HEADER USE
// check to see if our extra data is OURS. if so, process it
// cafeʞ, as UN signed bytes is: [254, 202, 202, 158], or as hex: FECA CA9E
// cafeʞ, as signed bytes is: [-2, -54, -54, -98]
private static final byte[] CUSTOM_HEADER = new byte[] {(byte) -2, (byte) -54, (byte) -54, (byte) -98};
public static
void addProvider() {
@ -67,293 +79,308 @@ class Crypto {
}
}
private
Crypto() {
public static
byte[] hashFileMD5(File file) {
try {
return MD5.getHash(file);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static
byte[] hashFileSHA1(File file) {
SHA1Digest digest = new SHA1Digest();
return hashFile(file, digest, null);
}
public static final
class Util {
public static
byte[] hashFileSHA256(File file) {
SHA256Digest digest = new SHA256Digest();
return hashFile(file, digest, null);
}
// CUSTOM_HEADER USE
private static final byte[] CUSTOM_HEADER = new byte[] {(byte) -2, (byte) -54, (byte) -54, (byte) -98};
public static
byte[] hashFileSHA512(File file) {
SHA512Digest digest = new SHA512Digest();
return hashFile(file, digest, null);
}
/**
* Return the hash of the file or NULL if file is invalid
*
* @param logger
* may be null, if no log output is necessary
*/
public static
byte[] hashFile(File file, Digest digest, Logger logger) {
return hashFile(file, digest, 0L, logger);
}
/**
* Return the hash of the file or NULL if file is invalid
*
* @param logger
* may be null, if no log output is necessary
*/
public static
byte[] hashFile(File file, Digest digest, Logger logger) {
return hashFile(file, digest, 0L, logger);
}
/**
* Return the hash of the file or NULL if file is invalid
*
* @param logger
* may be null, if no log output is necessary
*/
public static
byte[] hashFile(File file, Digest digest, long lengthFromEnd, Logger logger) {
return hashFile(file, digest, lengthFromEnd, null, logger);
}
/**
* Return the hash of the file or NULL if the file is invalid. ALSO includes the hash of the 'extraData' if specified.
*
* @param logger
* may be null, if no log output is necessary
*/
public static
byte[] hashFile(File file, Digest digest, long lengthFromEnd, byte[] extraData, Logger logger) {
if (file.isFile() && file.canRead()) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
long size = file.length();
if (lengthFromEnd > 0 && lengthFromEnd < size) {
size -= lengthFromEnd;
}
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int readBytes;
digest.reset();
while (size > 0) {
//noinspection NumericCastThatLosesPrecision
int maxToRead = (int) Math.min(bufferSize, size);
readBytes = inputStream.read(buffer, 0, maxToRead);
size -= readBytes;
if (readBytes == 0) {
//wtf. finally still gets called.
return null;
}
digest.update(buffer, 0, readBytes);
}
} catch (Exception e) {
if (logger != null) {
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (extraData != null) {
digest.update(extraData, 0, extraData.length);
}
byte[] digestBytes = new byte[digest.getDigestSize()];
digest.doFinal(digestBytes, 0);
return digestBytes;
}
else {
return null;
}
}
/**
* Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files.
*/
public static
byte[] hashJarContentsExcludeAction(File jarDestFilename, Digest digest, int action) throws IOException {
JarFile jarDestFile = new JarFile(jarDestFilename);
/**
* Return the hash of the file or NULL if file is invalid
*
* @param logger
* may be null, if no log output is necessary
*/
public static
byte[] hashFile(File file, Digest digest, long lengthFromEnd, Logger logger) {
return hashFile(file, digest, lengthFromEnd, null, logger);
}
/**
* Return the hash of the file or NULL if the file is invalid. ALSO includes the hash of the 'extraData' if specified.
*
* @param logger
* may be null, if no log output is necessary
*/
public static
byte[] hashFile(File file, Digest digest, long lengthFromEnd, byte[] extraData, Logger logger) {
if (file.isFile() && file.canRead()) {
InputStream inputStream = null;
try {
Enumeration<JarEntry> jarElements = jarDestFile.entries();
inputStream = new FileInputStream(file);
long size = file.length();
boolean okToHash;
boolean hasAction;
byte[] buffer = new byte[2048];
int read;
if (lengthFromEnd > 0 && lengthFromEnd < size) {
size -= lengthFromEnd;
}
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int readBytes;
digest.reset();
while (jarElements.hasMoreElements()) {
JarEntry jarEntry = jarElements.nextElement();
String name = jarEntry.getName();
okToHash = !jarEntry.isDirectory();
while (size > 0) {
//noinspection NumericCastThatLosesPrecision
int maxToRead = (int) Math.min(bufferSize, size);
readBytes = inputStream.read(buffer, 0, maxToRead);
size -= readBytes;
if (!okToHash) {
continue;
if (readBytes == 0) {
//wtf. finally still gets called.
return null;
}
// data with NO extra data will NOT BE HASHED
// data that matches our action bitmask WILL NOT BE HASHED
okToHash = false;
hasAction = false;
byte[] extraData = jarEntry.getExtra();
if (extraData == null || extraData.length == 0) {
okToHash = false;
}
else if (extraData.length >= 4) {
for (int i = 0; i < CUSTOM_HEADER.length; i++) {
if (extraData[i] != CUSTOM_HEADER[i]) {
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
}
}
// this means we matched our header
if (extraData[4] > 0) {
hasAction = true;
// we have an ACTION describing how it was compressed, etc
int fileAction = LittleEndian.Int_.from(new byte[] {extraData[5], extraData[6], extraData[7], extraData[8]});
if ((fileAction & action) != action) {
okToHash = true;
}
}
else {
okToHash = true;
}
}
else {
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
}
// skips hashing lgpl files. (technically, whatever our action bitmask is...)
// we want to hash everything BY DEFAULT. we ALSO want to hash the NAME, LOAD ACTION TYPE, and the contents
if (okToHash) {
// System.err.println("HASHING: " + name);
// hash the file name
byte[] bytes = name.getBytes(OS.US_ASCII);
digest.update(bytes, 0, bytes.length);
if (hasAction) {
// hash the action - since we don't want to permit anyone to change this after we sign the file
digest.update(extraData, 5, 4);
}
// hash the contents
InputStream inputStream = jarDestFile.getInputStream(jarEntry);
while ((read = inputStream.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
inputStream.close();
}
//else {
// System.err.println("Skipping: " + name);
//}
digest.update(buffer, 0, readBytes);
}
} catch (Exception e) {
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
if (logger != null) {
logger.error("Error hashing file: {}", file.getAbsolutePath(), e);
} else {
e.printStackTrace();
}
} finally {
jarDestFile.close();
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (extraData != null) {
digest.update(extraData, 0, extraData.length);
}
byte[] digestBytes = new byte[digest.getDigestSize()];
digest.doFinal(digestBytes, 0);
return digestBytes;
}
else {
return null;
}
}
/**
* Hash an input stream, based on the specified digest
*/
public static
byte[] hashStream(Digest digest, InputStream inputStream) throws IOException {
/**
* Specifically, to return the hash of the ALL files/directories inside the jar, minus the action specified (LGPL) files.
*/
public static
byte[] hashJarContentsExcludeAction(File jarDestFilename, Digest digest, int action) throws IOException {
JarFile jarDestFile = new JarFile(jarDestFilename);
try {
Enumeration<JarEntry> jarElements = jarDestFile.entries();
boolean okToHash;
boolean hasAction;
byte[] buffer = new byte[2048];
int read;
digest.reset();
while (jarElements.hasMoreElements()) {
JarEntry jarEntry = jarElements.nextElement();
String name = jarEntry.getName();
okToHash = !jarEntry.isDirectory();
while ((read = inputStream.read(buffer)) > 0) {
digest.update(buffer, 0, read);
if (!okToHash) {
continue;
}
// data with NO extra data will NOT BE HASHED
// data that matches our action bitmask WILL NOT BE HASHED
okToHash = false;
hasAction = false;
byte[] extraData = jarEntry.getExtra();
if (extraData == null || extraData.length == 0) {
okToHash = false;
}
else if (extraData.length >= 4) {
for (int i = 0; i < CUSTOM_HEADER.length; i++) {
if (extraData[i] != CUSTOM_HEADER[i]) {
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
}
}
// this means we matched our header
if (extraData[4] > 0) {
hasAction = true;
// we have an ACTION describing how it was compressed, etc
int fileAction = LittleEndian.Int_.from(new byte[] {extraData[5], extraData[6], extraData[7], extraData[8]});
if ((fileAction & action) != action) {
okToHash = true;
}
}
else {
okToHash = true;
}
}
else {
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
}
// skips hashing lgpl files. (technically, whatever our action bitmask is...)
// we want to hash everything BY DEFAULT. we ALSO want to hash the NAME, LOAD ACTION TYPE, and the contents
if (okToHash) {
// System.err.println("HASHING: " + name);
// hash the file name
byte[] bytes = name.getBytes(OS.US_ASCII);
digest.update(bytes, 0, bytes.length);
if (hasAction) {
// hash the action - since we don't want to permit anyone to change this after we sign the file
digest.update(extraData, 5, 4);
}
// hash the contents
InputStream inputStream = jarDestFile.getInputStream(jarEntry);
while ((read = inputStream.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
inputStream.close();
}
//else {
// System.err.println("Skipping: " + name);
//}
}
inputStream.close();
byte[] digestBytes = new byte[digest.getDigestSize()];
digest.doFinal(digestBytes, 0);
return digestBytes;
} catch (Exception e) {
throw new RuntimeException("Unexpected extra data in zip assigned. Aborting");
} finally {
jarDestFile.close();
}
/**
* Secure way to generate an AES key based on a password. Will '*' out the passed-in password
*
* @param password
* will be filled with '*'
* @param salt
* should be a RANDOM number, at least 256bits (32 bytes) in size.
* @param iterationCount
* should be a lot, like 10,000
*
* @return the secure key to use
*/
public static
byte[] PBKDF2(char[] password, byte[] salt, int iterationCount) {
// will also zero out the password.
byte[] charToBytes = Crypto.Util.charToBytesPassword(password);
byte[] digestBytes = new byte[digest.getDigestSize()];
return PBKDF2(charToBytes, salt, iterationCount);
}
/**
* Secure way to generate an AES key based on a password.
*
* @param password
* The password that you want to mix
* @param salt
* should be a RANDOM number, at least 256bits (32 bytes) in size.
* @param iterationCount
* should be a lot, like 10,000
*
* @return the secure key to use
*/
public static
byte[] PBKDF2(byte[] password, byte[] salt, int iterationCount) {
SHA256Digest digest = new SHA256Digest();
PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(digest);
pGen.init(password, salt, iterationCount);
KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); // *8 for bit length.
// zero out the password.
Arrays.fill(password, (byte) 0);
return key.getKey();
}
/**
* this saves the char array in UTF-16 format of bytes and BLANKS out the password char array.
*/
public static
byte[] charToBytesPassword(char[] password) {
// note: this saves the char array in UTF-16 format of bytes.
byte[] passwordBytes = new byte[password.length * 2];
for (int i = 0; i < password.length; i++) {
//noinspection NumericCastThatLosesPrecision
passwordBytes[2 * i] = (byte) (((int) password[i] & 0xFF00) >> 8);
//noinspection NumericCastThatLosesPrecision
passwordBytes[2 * i + 1] = (byte) ((int) password[i] & 0x00FF);
}
// asterisk out the password
Arrays.fill(password, '*');
return passwordBytes;
}
private
Util() {
}
digest.doFinal(digestBytes, 0);
return digestBytes;
}
/**
* Hash an input stream, based on the specified digest
*/
public static
byte[] hashStream(Digest digest, InputStream inputStream) throws IOException {
byte[] buffer = new byte[2048];
int read;
digest.reset();
while ((read = inputStream.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
inputStream.close();
byte[] digestBytes = new byte[digest.getDigestSize()];
digest.doFinal(digestBytes, 0);
return digestBytes;
}
/**
* Secure way to generate an AES key based on a password. Will '*' out the passed-in password
*
* @param password
* will be filled with '*'
* @param salt
* should be a RANDOM number, at least 256bits (32 bytes) in size.
* @param iterationCount
* should be a lot, like 10,000
*
* @return the secure key to use
*/
public static
byte[] PBKDF2(char[] password, byte[] salt, int iterationCount) {
// will also zero out the password.
byte[] charToBytes = Crypto.charToBytesPassword_UTF16(password);
return PBKDF2(charToBytes, salt, iterationCount);
}
/**
* Secure way to generate an AES key based on a password.
*
* @param password
* The password that you want to mix
* @param salt
* should be a RANDOM number, at least 256bits (32 bytes) in size.
* @param iterationCount
* should be a lot, like 10,000
*
* @return the secure key to use
*/
public static
byte[] PBKDF2(byte[] password, byte[] salt, int iterationCount) {
SHA256Digest digest = new SHA256Digest();
PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(digest);
pGen.init(password, salt, iterationCount);
KeyParameter key = (KeyParameter) pGen.generateDerivedMacParameters(digest.getDigestSize() * 8); // *8 for bit length.
// zero out the password.
Arrays.fill(password, (byte) 0);
return key.getKey();
}
/**
* this saves the char array in UTF-16 format of bytes and BLANKS out the password char array.
*/
public static
byte[] charToBytesPassword_UTF16(char[] password) {
// note: this saves the char array in UTF-16 format of bytes.
byte[] passwordBytes = new byte[password.length * 2];
for (int i = 0; i < password.length; i++) {
//noinspection NumericCastThatLosesPrecision
passwordBytes[2 * i] = (byte) (((int) password[i] & 0xFF00) >> 8);
//noinspection NumericCastThatLosesPrecision
passwordBytes[2 * i + 1] = (byte) ((int) password[i] & 0x00FF);
}
// asterisk out the password
Arrays.fill(password, '*');
return passwordBytes;
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
import org.bouncycastle.crypto.BufferedBlockCipher;

View File

@ -1,3 +1,18 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;

View File

@ -1,3 +1,18 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;

View File

@ -0,0 +1,815 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
import dorkbox.util.OS;
import dorkbox.util.Sys;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* PGP crypto related methods
*/
public final
class CryptoPGP {
private static final BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
private static final BcKeyFingerprintCalculator fingerprintCalculator = new BcKeyFingerprintCalculator();
// https://github.com/weiliatgithub/bouncycastle-gpg-exampleC
// https://gist.github.com/turingbirds/3df43f1920a98010667a
// http://sloanseaman.com/wordpress/2012/05/13/revisited-pgp-encryptiondecryption-in-java/
// http://bouncycastle-pgp-cookbook.blogspot.de/
/**
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
*
* @param privateKeyInputStream
* this is an armored key file, not a binary stream
* @param userId
* this is the userID to get out of the private key
* @param password
* this is the password to unlock the private key
* @param messageAsUtf8Bytes
* this is the message, in bytes, to sign
*/
public static
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, byte[] messageAsUtf8Bytes)
throws PGPException {
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
return sign(privateKeyInputStream,
userId,
password,
new ByteArrayInputStream(messageAsUtf8Bytes),
PGPSignature.BINARY_DOCUMENT,
false,
true,
false,
false,
false);
}
/**
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt"
*
* @param privateKeyInputStream
* this is an armored key file, not a binary stream
* @param userId
* this is the userID to get out of the private key
* @param password
* this is the password to unlock the private key
* @param message
* this is the message to sign
*/
public static
byte[] signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, InputStream message)
throws PGPException {
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
return sign(privateKeyInputStream,
userId,
password,
message,
PGPSignature.BINARY_DOCUMENT,
false,
true,
false,
false,
false);
}
/**
* Sign a message using our private PGP key file, this matches gpg -ab "hello.txt". This will save the signature of the passed-in
* file to file name + .asc
*
* @param privateKeyInputStream
* this is an armored key file, not a binary stream
* @param userId
* this is the userID to get out of the private key
* @param password
* this is the password to unlock the private key
* @param file
* this is the file to sign
*/
public static
void signGpgCompatible(InputStream privateKeyInputStream, String userId, char[] password, File file)
throws PGPException {
// the signature type (in gpg terms), is "sigclass". gpg is BINARY_DOC (0x00)
final byte[] sign = sign(privateKeyInputStream,
userId,
password,
file,
PGPSignature.BINARY_DOCUMENT,
false,
true,
false,
false,
false);
FileOutputStream fileOutputStream1 = null;
try {
fileOutputStream1 = new FileOutputStream(new File(file.getAbsolutePath() + ".asc"));
fileOutputStream1.write(sign);
fileOutputStream1.flush();
} catch (FileNotFoundException e) {
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
} catch (IOException e) {
throw new PGPException("Unable to save signature to file " + file.getAbsolutePath() + ".asc", e);
} finally {
Sys.close(fileOutputStream1);
}
}
/**
* Sign a message using our private PGP key file, with a variety of options
*/
@SuppressWarnings("Duplicates")
public static
byte[] sign(InputStream privateKeyInputStream,
String userId,
char[] password,
InputStream message,
int signatureType,
boolean compressSignature,
boolean asciiArmoredOutput,
boolean includeDataInSignature,
boolean generateUserIdSubPacket,
boolean generateOnePassVersion) throws PGPException {
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStream outputStream = byteArrayOutputStream;
if (asciiArmoredOutput) {
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
}
PGPCompressedDataGenerator compressedDataGenerator = null;
BCPGOutputStream bcOutputStream;
if (compressSignature) {
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
try {
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
} catch (IOException e) {
throw new PGPException("Unable to open compression stream in the signature", e);
}
}
else {
bcOutputStream = new BCPGOutputStream(outputStream);
}
if (generateOnePassVersion) {
try {
signature.generateOnePassVersion(false)
.encode(bcOutputStream);
} catch (IOException e) {
throw new PGPException("Unable to generate OnePass signature header", e);
}
}
PGPLiteralDataGenerator literalDataGenerator = null;
OutputStream literalDataOutput = null;
if (includeDataInSignature) {
literalDataGenerator = new PGPLiteralDataGenerator();
try {
literalDataOutput = literalDataGenerator.open(bcOutputStream,
PGPLiteralData.BINARY,
"_CONSOLE",
message.available(),
new Date());
} catch (IOException e1) {
throw new PGPException("Unable to generate Literal Data signature header", e1);
}
}
try {
byte[] buffer = new byte[4096];
int read;
// update bytes in the streams
if (literalDataOutput != null) {
while ((read = message.read(buffer)) > 0) {
literalDataOutput.write(buffer, 0, read);
signature.update(buffer, 0, read);
}
literalDataOutput.flush();
} else {
while ((read = message.read(buffer)) > 0) {
signature.update(buffer, 0, read);
}
}
// close generators and update signature
if (literalDataGenerator != null) {
literalDataGenerator.close();
}
signature.generate()
.encode(bcOutputStream);
if (compressedDataGenerator != null) {
compressedDataGenerator.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Sys.close(bcOutputStream);
Sys.close(outputStream);
Sys.close(literalDataOutput);
}
return byteArrayOutputStream.toByteArray();
}
/**
* Sign a message using our private PGP key file, with a variety of options
*/
@SuppressWarnings("Duplicates")
public static
byte[] sign(InputStream privateKeyInputStream,
String userId,
char[] password,
File fileMessage,
int signatureType,
boolean compressSignature,
boolean asciiArmoredOutput,
boolean includeDataInSignature,
boolean generateUserIdSubPacket,
boolean generateOnePassVersion) throws PGPException {
List<PGPSecretKey> secretKeys = getSecretKeys(privateKeyInputStream, userId);
PGPSignatureGenerator signature = createSignature(secretKeys, password, signatureType, generateUserIdSubPacket);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStream outputStream = byteArrayOutputStream;
if (asciiArmoredOutput) {
outputStream = new ArmoredOutputStream(byteArrayOutputStream);
}
PGPCompressedDataGenerator compressedDataGenerator = null;
BCPGOutputStream bcOutputStream;
if (compressSignature) {
compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
try {
bcOutputStream = new BCPGOutputStream(compressedDataGenerator.open(outputStream));
} catch (IOException e) {
throw new PGPException("Unable to open compression stream in the signature", e);
}
}
else {
bcOutputStream = new BCPGOutputStream(outputStream);
}
if (generateOnePassVersion) {
try {
signature.generateOnePassVersion(false)
.encode(bcOutputStream);
} catch (IOException e) {
throw new PGPException("Unable to generate OnePass signature header", e);
}
}
PGPLiteralDataGenerator literalDataGenerator = null;
OutputStream literalDataOutput = null;
if (includeDataInSignature) {
literalDataGenerator = new PGPLiteralDataGenerator();
try {
literalDataOutput = literalDataGenerator.open(bcOutputStream,
PGPLiteralData.BINARY,
fileMessage);
} catch (IOException e1) {
throw new PGPException("Unable to generate Literal Data signature header", e1);
}
}
try {
final FileInputStream fileInputStream = new FileInputStream(fileMessage);
byte[] buffer = new byte[4096];
int read;
// update bytes in the streams
if (literalDataOutput != null) {
while ((read = fileInputStream.read(buffer)) > 0) {
literalDataOutput.write(buffer, 0, read);
signature.update(buffer, 0, read);
}
literalDataOutput.flush();
} else {
while ((read = fileInputStream.read(buffer)) > 0) {
signature.update(buffer, 0, read);
}
}
// close generators and update signature
if (literalDataGenerator != null) {
literalDataGenerator.close();
}
signature.generate()
.encode(bcOutputStream);
if (compressedDataGenerator != null) {
compressedDataGenerator.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Sys.close(bcOutputStream);
Sys.close(outputStream);
Sys.close(literalDataOutput);
}
return byteArrayOutputStream.toByteArray();
}
/**
* Find private gpg key in InputStream, also closes the input stream
*
* @param inputStream
* the inputStream that contains the private (secret) key
* @param userId
* the user id
*
* @return the PGP secret key
*/
public static
List<PGPSecretKey> getSecretKeys(InputStream inputStream, String userId) throws PGPException {
// iterate over every private key in the key ring
PGPSecretKeyRingCollection secretKeyRings;
try {
secretKeyRings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
} catch (IOException e) {
throw new PGPException("No private key found in stream!", e);
} finally {
Sys.close(inputStream);
}
// look for the key ring that is used to authenticate our reporting facilities
Iterator<PGPSecretKeyRing> secretKeys = secretKeyRings.getKeyRings(userId);
List<PGPSecretKey> pgpSecretKeys = new ArrayList<PGPSecretKey>();
// iterate over every private key in the ring
while (secretKeys.hasNext()) {
PGPSecretKeyRing secretKeyRing = secretKeys.next();
PGPSecretKey tmpKey = secretKeyRing.getSecretKey();
if (tmpKey != null) {
pgpSecretKeys.add(tmpKey);
}
}
if (!pgpSecretKeys.isEmpty()) {
return pgpSecretKeys;
}
throw new PGPException("No private key found in stream!");
}
/**
* Creates the signature that will be used to PGP sign data
*
* @param secretKeys
* these are the secret keys
* @param password
* this is the password to unlock the secret key
*
* @return the signature used to sign data
*
* @throws PGPException
*/
private static
PGPSignatureGenerator createSignature(List<PGPSecretKey> secretKeys,
char[] password,
int signatureType,
boolean generateUserIdSubPacket) throws PGPException {
PGPSecretKey secretKey = null;
for (int i = 0; i < secretKeys.size(); i++) {
secretKey = secretKeys.get(i);
// we ONLY want the signing master key
if (!secretKey.isSigningKey() || !secretKey.isMasterKey()) {
secretKey = null;
}
}
if (secretKey == null) {
throw new PGPException("Secret key is not the signing master key");
}
// System.err.println("Signing key = " + tmpKey.isSigningKey() +", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
// userId );
if (password == null) {
password = new char[0];
}
PBESecretKeyDecryptor build = new BcPBESecretKeyDecryptorBuilder(digestCalculatorProvider).build(password);
SecureRandom random = new SecureRandom();
BcPGPContentSignerBuilder bcPGPContentSignerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey()
.getAlgorithm(),
PGPUtil.SHA1).setSecureRandom(random);
PGPSignatureGenerator signature = new PGPSignatureGenerator(bcPGPContentSignerBuilder);
signature.init(signatureType, secretKey.extractPrivateKey(build));
Iterator userIds = secretKey.getPublicKey()
.getUserIDs();
// use the first userId that matches
if (userIds.hasNext()) {
if (generateUserIdSubPacket) {
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
subpacketGenerator.setSignerUserID(false, (String) userIds.next());
signature.setHashedSubpackets(subpacketGenerator.generate());
}
else {
signature.setHashedSubpackets(null);
}
return signature;
}
else {
throw new PGPException("Did not find specified userId");
}
}
/**
* Decode a PGP public key block and return the keyring it represents.
*/
public static
PGPPublicKeyRing getKeyring(InputStream keyBlockStream) throws IOException {
BcKeyFingerprintCalculator keyfp = new BcKeyFingerprintCalculator();
// PGPUtil.getDecoderStream() will detect ASCII-armor automatically and decode it,
// the PGPObject factory then knows how to read all the data in the encoded stream
PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(keyBlockStream), keyfp);
// these files should really just have one object in them, and that object should be a PGPPublicKeyRing.
Object o = factory.nextObject();
if (o instanceof PGPPublicKeyRing) {
return (PGPPublicKeyRing) o;
}
throw new IllegalArgumentException("Input stream does not contain a PGP Public Key");
}
/**
* Get the first encryption key from the given keyring.
*/
public static
PGPPublicKey getEncryptionKey(PGPPublicKeyRing keyRing) {
if (keyRing == null) {
return null;
}
// iterate over the keys on the ring, look for one which is suitable for encryption.
Iterator keys = keyRing.getPublicKeys();
PGPPublicKey key;
while (keys.hasNext()) {
key = (PGPPublicKey) keys.next();
if (key.isEncryptionKey()) {
return key;
}
}
return null;
}
/**
* Get the first decryption key from the given keyring.
*/
public
PGPSecretKey getDecryptionKey(PGPSecretKeyRing keyRing) {
if (keyRing == null) {
return null;
}
// iterate over the keys on the ring, look for one which is suitable for encryption.
Iterator keys = keyRing.getSecretKeys();
PGPSecretKey key;
while (keys.hasNext()) {
key = (PGPSecretKey) keys.next();
if (key.isMasterKey()) {
return key;
}
}
return null;
}
/**
* Encrypt plaintext message using public key from publickeyFile.
*
* @param message
* the message
*
* @return the string
*/
private
String encrypt(InputStream publicKeyInputStream, String message) throws PGPException, IOException, NoSuchProviderException {
// find the PGP key in the file
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
if (publicKey == null) {
System.err.println("Did not find public GPG key");
return null;
}
// Encode the string into bytes using utf-8
byte[] utf8Bytes = message.getBytes(OS.UTF_8);
ByteArrayOutputStream compressedOutput = new ByteArrayOutputStream();
// compress bytes with zip
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
// the reason why we compress here is GPG not being able to decrypt our message input but if we do not compress.
// I guess pkzip compression also encodes only to GPG-friendly characters.
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
try {
OutputStream literalDataOutput = literalDataGenerator.open(compressedOutput,
PGPLiteralData.BINARY,
"_CONSOLE",
utf8Bytes.length,
new Date());
// update bytes in the stream
literalDataOutput.write(utf8Bytes);
} catch (IOException e) {
// catch but close the streams in finally
throw e;
} finally {
compressedDataGenerator.close();
Sys.close(compressedOutput);
}
SecureRandom random = new SecureRandom();
// now we have zip-compressed bytes
byte[] compressedBytes = compressedOutput.toByteArray();
BcPGPDataEncryptorBuilder bcPGPDataEncryptorBuilder = new BcPGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
.setWithIntegrityPacket(true)
.setSecureRandom(random);
PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(bcPGPDataEncryptorBuilder);
// use public key to encrypt data
BcPublicKeyKeyEncryptionMethodGenerator encKeyGen = new BcPublicKeyKeyEncryptionMethodGenerator(publicKey)
.setSecureRandom(random);
encryptedDataGenerator.addMethod(encKeyGen);
// literalDataOutput --> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ArmoredOutputStream armoredOut = new ArmoredOutputStream(byteArrayOutputStream);
OutputStream encryptedOutput = null;
try {
encryptedOutput = encryptedDataGenerator.open(armoredOut, compressedBytes.length);
encryptedOutput.write(compressedBytes);
} catch (IOException e) {
throw e;
} catch (PGPException e) {
throw e;
} finally {
Sys.close(encryptedOutput);
Sys.close(armoredOut);
}
String encrypted = new String(byteArrayOutputStream.toByteArray());
System.err.println("Message: " + message);
System.err.println("Encrypted: " + encrypted);
return encrypted;
}
/**
* Find public gpg key in InputStream.
*
* @param inputStream
* the input stream
*
* @return the PGP public key
*/
private static
PGPPublicKey findPublicGPGKey(InputStream inputStream) throws IOException, PGPException {
// get all key rings in the input stream
PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(inputStream), fingerprintCalculator);
System.err.println("key ring size: " + publicKeyRingCollection.size());
Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRingCollection.getKeyRings();
// iterate over keyrings
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
// iterate over public keys in the key ring
while (keyIter.hasNext()) {
PGPPublicKey tmpKey = keyIter.next();
if (tmpKey == null) {
break;
}
Iterator<String> userIDs = tmpKey.getUserIDs();
ArrayList<String> strings = new ArrayList<String>();
while (userIDs.hasNext()) {
String next = userIDs.next();
strings.add(next);
}
System.err.println(
"Encryption key = " + tmpKey.isEncryptionKey() + ", Master key = " + tmpKey.isMasterKey() + ", UserId = " +
strings);
// we need a master encryption key
if (tmpKey.isEncryptionKey() && tmpKey.isMasterKey()) {
return tmpKey;
}
}
}
throw new PGPException("No public key found!");
}
private static
void verify(final InputStream publicKeyInputStream, final byte[] signature) throws Exception {
PGPPublicKey publicKey = findPublicGPGKey(publicKeyInputStream);
String text = new String(signature);
Pattern regex = Pattern.compile(
"-----BEGIN PGP SIGNED MESSAGE-----\\r?\\n.*?\\r?\\n\\r?\\n(.*)\\r?\\n(-----BEGIN PGP SIGNATURE-----\\r?\\n.*-----END PGP SIGNATURE-----)",
Pattern.CANON_EQ | Pattern.DOTALL);
Matcher regexMatcher = regex.matcher(text);
if (regexMatcher.find()) {
String dataText = regexMatcher.group(1);
String signText = regexMatcher.group(2);
ByteArrayInputStream dataIn = new ByteArrayInputStream(dataText.getBytes("UTF8"));
ByteArrayInputStream signIn = new ByteArrayInputStream(signText.getBytes("UTF8"));
InputStream signIn2 = PGPUtil.getDecoderStream(signIn);
PGPObjectFactory pgpFact = new PGPObjectFactory(signIn2, new BcKeyFingerprintCalculator());
PGPSignatureList p3 = null;
Object o;
try {
o = pgpFact.nextObject();
if (o == null) {
throw new Exception();
}
} catch (Exception ex) {
throw new Exception("Invalid input data");
}
if (o instanceof PGPCompressedData) {
PGPCompressedData c1 = (PGPCompressedData) o;
pgpFact = new PGPObjectFactory(c1.getDataStream(), new BcKeyFingerprintCalculator());
p3 = (PGPSignatureList) pgpFact.nextObject();
}
else {
p3 = (PGPSignatureList) o;
}
// PGPSignature sig = p3.get(0);
// PGPPublicKey key = KeyRing.getPublicKeyByID(sig.getKeyID());
//
// if (key == null)
// throw new Exception("Cannot find key 0x" + Integer.toHexString((int) sig.getKeyID()).toUpperCase() + " in the pubring");
//
// sig.initVerify(key, "BC");
//
// while ((ch = dataIn.read()) >= 0) {
// sig.update((byte) ch); //TODO migliorabile con byte[]
// }
//
// if (sig.verify())
// return new PrintablePGPPublicKey(key).toString();
// else
// return null;
// return verifyFile(dataIn, signIn);
}
}
private
CryptoPGP() {
}
public static
void main(String[] args) throws Exception {
InputStream privateKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_private.key"));
byte[] textBytes = "hello".getBytes(OS.UTF_8);
byte[] bytes = CryptoPGP.signGpgCompatible(privateKeyInputStream, "Dorkbox <sonatype@dorkbox.com>", new char[0], textBytes);
// String s = new String(hello);
// String s1 = s.replaceAll("\n", "\r\n");
// byte[] bytes = s1.getBytes(OS.UTF_8);
//
// String signed = new String(bytes);
//
// System.err.println("Message: " + new String(messageAsUtf8Bytes));
// System.err.println("Signature: " + signed);
//
// return bytes;
// String s2 = new String(bytes);
// InputStream publicKeyInputStream = new FileInputStream(new File("/home/user/dorkbox/sonatype_public.key"));
// cryptoPGP.verify(publicKeyInputStream, hello);
FileOutputStream fileOutputStream = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt"));
fileOutputStream.write(textBytes);
fileOutputStream.flush();
Sys.close(fileOutputStream);
FileOutputStream fileOutputStream1 = new FileOutputStream(new File("/home/user/dorkbox/hello2.txt.asc"));
fileOutputStream1.write(bytes);
fileOutputStream1.flush();
Sys.close(fileOutputStream1);
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
import org.bouncycastle.crypto.AsymmetricBlockCipher;

View File

@ -1,3 +1,18 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.crypto;
import java.security.SecureRandom;
@ -86,7 +101,7 @@ class CryptoSCrypt {
String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
// Note: this saves the char array in UTF-16 format of bytes.
// can't use password after this as it's been changed to '*'
byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
@ -118,7 +133,7 @@ class CryptoSCrypt {
boolean verify(char[] password, String hashed) {
// Note: this saves the char array in UTF-16 format of bytes.
// can't use password after this as it's been changed to '*'
byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
byte[] passwordBytes = Crypto.charToBytesPassword_UTF16(password);
String[] parts = hashed.split("\\$");