/**
* OnionCoffee - Anonymous Communication through TOR Network
* Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package TorJava.Common;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPublicKeySpec;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.encodings.OAEPEncoding;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.jce.provider.JCERSAPrivateKey;
import org.bouncycastle.jce.provider.JCERSAPublicKey;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.util.encoders.Base64;
import TorJava.Logger;
/**
* this class contains utility functions concerning encryption
*
* @author Lexi Pimenidis
* @author Andriy Panchenko
* @author Michael Koellejan
*/
public class Encryption {
/**
* returns the hash of the input
*
*
*/
public static byte[] getHash(byte[] input) {
SHA1Digest sha1 = new SHA1Digest();
sha1.reset();
sha1.update(input, 0, input.length);
byte[] hash = new byte[sha1.getDigestSize()];
sha1.doFinal(hash, 0);
return hash;
}
/**
* checks signature of PKCS1-padded SHA1 hash of the input
*
* @param signature
* signature to check
* @param signingKey
* public key from signing
* @param input
* byte array, signature is made over
*
* @return true, if the signature is correct
*
*/
public static boolean verifySignature(byte[] signature,
RSAPublicKeyStructure signingKey, byte[] input) {
byte[] hash = getHash(input);
try {
RSAKeyParameters myRSAKeyParameters = new RSAKeyParameters(false,
signingKey.getModulus(), signingKey.getPublicExponent());
PKCS1Encoding pkcs_alg = new PKCS1Encoding(new RSAEngine());
pkcs_alg.init(false, myRSAKeyParameters);
byte[] decrypted_signature = pkcs_alg.processBlock(signature, 0,
signature.length);
return Encoding.arraysEqual(hash, decrypted_signature);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* checks row signature
*
* @param signature
* signature to check
* @param signingKey
* public key from signing
* @param input
* byte array, signature is made over
*
* @return true, if the signature is correct
*
*/
public static boolean verifySignatureXXXX(byte[] signature,
RSAPublicKeyStructure signingKey, byte[] input) {
byte[] hash = getHash(input);
try {
Signature sig = Signature.getInstance("SHA1withRSA");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(signingKey
.getModulus(), signingKey.getPublicExponent());
PublicKey pubKey = keyFactory.generatePublic(keySpec);
sig.initVerify(pubKey);
sig.update(input);
System.out.println("");
System.out.println(" HERE -> " + sig.verify(signature));
RSAKeyParameters myRSAKeyParameters = new RSAKeyParameters(false,
signingKey.getModulus(), signingKey.getPublicExponent());
RSAEngine rsa_alg = new RSAEngine();
rsa_alg.init(false, myRSAKeyParameters);
byte[] decrypted_signature = rsa_alg.processBlock(signature, 0,
signature.length);
System.out.println(" inpu = " + Encoding.toHexString(input));
System.out.println(" hash = " + Encoding.toHexString(hash));
System.out.println("");
System.out.println(" sign = " + Encoding.toHexString(signature));
System.out.println(" decr = "
+ Encoding.toHexString(decrypted_signature));
return Encoding.arraysEqual(hash, decrypted_signature);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* sign some data using a private kjey and PKCS#1 v1.5 padding
*
* @param data
* the data to be signed
* @param signingKey
* the key to sign the data
* @return a signature
*/
public static byte[] signData(byte[] data, RSAKeyParameters signingKey) {
try {
byte[] hash = Encryption.getHash(data);
PKCS1Encoding pkcs1 = new PKCS1Encoding(new RSAEngine());
pkcs1.init(true, signingKey);
return pkcs1.processBlock(hash, 0, hash.length);
} catch (InvalidCipherTextException e) {
Logger.logGeneral(Logger.ERROR, "Common.signData(): "
+ e.getMessage());
e.printStackTrace();
return null;
}
}
/** used to encode a signature in PEM */
public static String binarySignatureToPEM(byte[] signature) {
String sigB64 = Encoding.toBase64(signature);
StringBuffer sig = new StringBuffer();
sig.append("-----BEGIN SIGNATURE-----\n");
while (sigB64.length() > 64) {
sig.append(sigB64.substring(0, 64) + "\n");
sigB64 = sigB64.substring(64);
}
sig.append(sigB64 + "\n");
sig.append("-----END SIGNATURE-----\n");
return sig.toString();
}
/**
* makes RSA public key from string
*
* @param s
* string that contais the key
* @return
* @see JCERSAPublicKey
*/
public static RSAPublicKeyStructure extractRSAKey(String s) {
PEMReader reader = new PEMReader(new StringReader(s));
JCERSAPublicKey JCEKey;
RSAPublicKeyStructure theKey;
try {
Object o = reader.readObject();
if (!(o instanceof JCERSAPublicKey))
throw new IOException(
"Common.extractRSAKey: no public key found for signing key in string '"
+ s + "' type " + o.getClass().getName());
JCEKey = (JCERSAPublicKey) o;
theKey = new RSAPublicKeyStructure(JCEKey.getModulus(), JCEKey
.getPublicExponent());
} catch (IOException e) {
Logger.logDirectory(Logger.WARNING,
"Common.extractRSAKey: Caught exception:" + e.getMessage());
theKey = null;
}
return theKey;
}
/**
* makes RSA public key from bin byte array
*
* @param s
* string that contais the key
* @return
* @see JCERSAPublicKey
*/
public static RSAPublicKeyStructure extractBinaryRSAKey(byte[] b) {
RSAPublicKeyStructure theKey;
try {
ASN1InputStream ais = new ASN1InputStream(b);
Object asnObject = ais.readObject();
ASN1Sequence sequence = (ASN1Sequence) asnObject;
theKey = new RSAPublicKeyStructure(sequence);
} catch (IOException e) {
Logger.logDirectory(Logger.WARNING, "Caught exception:"
+ e.getMessage());
theKey = null;
}
return theKey;
}
/**
* copy from one format to another
*/
public static RSAPublicKeyStructure getRSAPublicKeyStructureFromJCERSAPublicKey(
JCERSAPublicKey jpub) {
return new RSAPublicKeyStructure(jpub.getModulus(), jpub
.getPublicExponent());
}
/**
* converts a JCERSAPublicKey into PKCS1-encoding
*
* @param rsaPublicKey
* @see JCERSAPublicKey
* @return PKCS1-encoded RSA PUBLIC KEY
*/
public static byte[] getPKCS1EncodingFromRSAPublicKey(
RSAPublicKeyStructure pubKeyStruct) {
try {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
ASN1OutputStream aOut = new ASN1OutputStream(bOut);
aOut.writeObject(pubKeyStruct.toASN1Object());
return bOut.toByteArray();
} catch (Exception e) {
return null;
}
}
/**
* converts a JCERSAPublicKey into PEM/PKCS1-encoding
*
* @param rsaPublicKey
* @see RSAPublicKeyStructure
* @return PEM-encoded RSA PUBLIC KEY
*/
public static String getPEMStringFromRSAPublicKey(RSAPublicKeyStructure rsaPublicKey) {
// mrk: this was awful to program. Remeber: There are two entirely
// different
// standard formats for rsa public keys. Bouncy castle does only support
// the
// one we can't use for TOR directories.
StringBuffer tmpDirSigningKey = new StringBuffer();
try {
tmpDirSigningKey.append("-----BEGIN RSA PUBLIC KEY-----\n");
byte[] base64Encoding = Base64
.encode(getPKCS1EncodingFromRSAPublicKey(rsaPublicKey));
for (int i = 0; i < base64Encoding.length; i++) {
tmpDirSigningKey.append((char) base64Encoding[i]);
if (((i + 1) % 64) == 0)
tmpDirSigningKey.append("\n");
}
tmpDirSigningKey.append("\n");
tmpDirSigningKey.append("-----END RSA PUBLIC KEY-----\n");
} catch (Exception e) {
return null;
}
return tmpDirSigningKey.toString();
}
/**
* makes RSA private key from base64 encoded string
*
* @param s
* string that contais the key
* @return
* @see JCERSAPPrivateKey
*/
public static JCERSAPrivateKey getRSAPrivateKeyFromPEMString(String s) {
PEMReader reader = new PEMReader(new StringReader(s));
JCERSAPrivateKey theKey;
try {
Object o = reader.readObject();
if (o instanceof JCERSAPrivateKey) {
theKey = (JCERSAPrivateKey) o;
} else if (o instanceof KeyPair) {
PrivateKey k = ((KeyPair)o).getPrivate();
if (k instanceof JCERSAPrivateKey) {
theKey = (JCERSAPrivateKey)k;
} else {
throw new IOException("key found is not RSA");
}
} else {
throw new IOException(
"no private key found for signing key in string '" + s
+ "'");
}
} catch (IOException e) {
Logger.logDirectory(Logger.WARNING, "Caught exception:"
+ e.getMessage());
theKey = null;
}
return theKey;
}
/**
* makes RSA public key from base64 encoded string
*
* @param s
* string that contais the key
* @return
* @see JCERSAPublicKey
*/
public static JCERSAPublicKey getRSAPublicKeyFromPEMString(String s) {
PEMReader reader = new PEMReader(new StringReader(s));
JCERSAPublicKey theKey;
try {
Object o = reader.readObject();
if (!(o instanceof JCERSAPublicKey))
throw new IOException(
"no public key found for signing key in string '" + s
+ "'");
theKey = (JCERSAPublicKey) o;
} catch (IOException e) {
Logger.logDirectory(Logger.WARNING, "Caught exception:"
+ e.getMessage());
theKey = null;
}
return theKey;
}
/**
* encrypt data with asymmetric key. create asymmetrically encrypted data:<br>
* <ul>
* <li>OAEP padding [42 bytes] (RSA-encrypted)
* <li>Symmetric key [16 bytes]
* <li>First part of data [70 bytes]
* <li>Second part of data [x-70 bytes] (Symmetrically encrypted)
* <ul>
* encrypt and store in result
*
* @param priv
* key to use for decryption
* @param data
* to be decrypted, needs currently to be at least 70 bytes long
* @return raw data
*/
public static byte[] asym_decrypt(RSAKeyParameters priv, byte[] data)
throws TorException {
if (data == null)
throw new NullPointerException("can't encrypt NULL data");
if (data.length < 70)
throw new TorException("input array too short");
try {
int encrypted_bytes = 0;
// init OAEP
OAEPEncoding oaep = new OAEPEncoding(new RSAEngine());
oaep.init(false, priv);
// apply RSA+OAEP
encrypted_bytes = oaep.getInputBlockSize();
byte[] oaep_input = new byte[encrypted_bytes];
System.arraycopy(data, 0, oaep_input, 0, encrypted_bytes);
byte[] part1 = oaep.decodeBlock(oaep_input, 0, encrypted_bytes);
// extract symmetric key
byte[] symmetric_key = new byte[16];
System.arraycopy(part1, 0, symmetric_key, 0, 16);
// init AES
AESCounterMode aes = new AESCounterMode(true, symmetric_key);
// apply AES
byte[] aes_input = new byte[data.length - encrypted_bytes];
System.arraycopy(data, encrypted_bytes, aes_input, 0, aes_input.length);
byte part2[] = aes.processStream(aes_input);
// replace unencrypted data
byte[] result = new byte[part1.length - 16 + part2.length];
System.arraycopy(part1, 16, result, 0, part1.length - 16);
System.arraycopy(part2, 0, result, part1.length - 16, part2.length);
return result;
} catch (InvalidCipherTextException e) {
Logger.logCell(Logger.ERROR,
"CommonEncryption.asym_decrypt(): can't decrypt cipher text:"
+ e.getMessage());
throw new TorException("CommonEncryption.asym_decrypt(): InvalidCipherTextException:"
+ e.getMessage());
}
}
/**
* converts a PEM-encoded private key back into JCERSAPublicKey
*
* @param rsaPrivateKey
* @see JCERSAPrivateKey
* @return PEM-encoded RSA PRIVATE KEY
*/
public static String getPEMStringFromRSAPrivateKey(JCERSAPrivateKey rsaPrivateKey) {
StringWriter pemStrWriter = new StringWriter();
PEMWriter pemWriter = new PEMWriter(pemStrWriter);
try {
pemWriter.writeObject(rsaPrivateKey);
pemWriter.close();
pemStrWriter.flush();
} catch (IOException e) {
Logger.logDirectory(Logger.WARNING, "Caught exception:"
+ e.getMessage());
return "";
}
return pemStrWriter.toString();
}
public static RSAPrivateKeyStructure getRSAPrivateKeyStructureFromJCERSAPrivateKey(JCERSAPrivateKey key,JCERSAPublicKey pubkey) {
return new RSAPrivateKeyStructure(key.getModulus(), pubkey.getPublicExponent(),
key.getPrivateExponent(), null, null, null, null, null);
}
}