diff options
author | Peter Palfrader <peter@palfrader.org> | 2003-10-09 11:41:45 +0000 |
---|---|---|
committer | Peter Palfrader <peter@palfrader.org> | 2003-10-09 11:41:45 +0000 |
commit | 566d17f731637df6828bdf32502a0fb123882dbe (patch) | |
tree | fc09fdfb90953134fa1d25f73367307502348a22 /src/org/noreply/fancydress/crypto | |
parent | 018eea460ee32df1b70c40c2eca05f06c06daca5 (diff) |
Initial import
Diffstat (limited to 'src/org/noreply/fancydress/crypto')
-rw-r--r-- | src/org/noreply/fancydress/crypto/CryptoPrimitives.java | 299 | ||||
-rw-r--r-- | src/org/noreply/fancydress/crypto/RSAPublicKey.java | 125 |
2 files changed, 424 insertions, 0 deletions
diff --git a/src/org/noreply/fancydress/crypto/CryptoPrimitives.java b/src/org/noreply/fancydress/crypto/CryptoPrimitives.java new file mode 100644 index 0000000..2f4fbd1 --- /dev/null +++ b/src/org/noreply/fancydress/crypto/CryptoPrimitives.java @@ -0,0 +1,299 @@ +package org.noreply.fancydress.crypto; + +import java.security.SecureRandom; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.params.KeyParameter; +import org.noreply.fancydress.misc.Util; + +/** + * This class implements all cryptographic primitives that are needed for a + * type III implementation. + */ +public class CryptoPrimitives { + /** + * length (in octets) of a symmetric AES-128 key. + */ + public static final int KEY_LEN = 16; + /** + * length (in octets) of our SPRP (LIONESS) key. + */ + public static final int SPRP_KEY_LEN = 20; + /** + * length (in octets) of our hash function's (SHA-1) output. + */ + public static final int HASH_LEN = 20; + + /** + * A secure pseudo random number generator. + */ + public static SecureRandom secureRandom = new SecureRandom(); + /** + * Our SHA-1 Digest. + */ + public static SHA1Digest digest = new SHA1Digest(); + /** + * The AES Engine used for symmetric operations. + */ + public static AESEngine aes_cipher = new AESEngine(); + + /** + * XOR two octet arrays. + * + * @param a octet array 1 + * @param b octet array 2 + * @return an octet array of equal length where x[i] = a[i] XOR b[i] for all i + * @throws Error if a and b do not have the same length + */ + public static byte[] xor(byte[] a, byte[] b) { + if (a.length != b.length) + throw new Error("Arguments to xor must have the same length"); + byte[] result = new byte[a.length]; + for (int i=0; i<a.length; i++) + result[i] = (byte) (a[i] ^ b[i]); + return(result); + } + /** + * Get <code>n</code> random octets from our secure pseudo random number generator. + * + * @param n number of octets + * @return an octet array of n octets of your fines randomness + */ + public static byte[] rand(int n) { + byte[] result = new byte[n]; + secureRandom.nextBytes(result); + return(result); + } + /** + * Get <code>n</code> octets of zeroes. + * + * @param n number of octets + * @return an octet array of n octets of zeroes + */ + public static byte[] zero(int n) { + byte[] result = new byte[n]; + return(result); + } + /** + * Hash the message provided in <code>m</code>. + * + * @param m a message + * @return the SHA-1 hash of <code>m</code> + */ + public static byte[] hash(byte[] m) { + digest.update(m, 0, m.length); + byte[] result = new byte[HASH_LEN]; + digest.doFinal(result, 0); + return(result); + } + /** + * Hash the result of the concatenation of <code>m1</code> and <code>m2</code>. + * + * @param m1 message 1 + * @param m2 message 2 + * @return the SHA-1 hash of <code>m1 + m2</code> + */ + public static byte[] hash(byte[] m1, byte[] m2) { + digest.update(m1, 0, m1.length); + digest.update(m2, 0, m2.length); + byte[] result = new byte[HASH_LEN]; + digest.doFinal(result, 0); + return(result); + } + /** + * Hash the result of the concatenation of <code>m1</code>, <code>m2</code> and <code>m3</code>. + * + * @param m1 message 1 + * @param m2 message 2 + * @param m3 message 3 + * @return the SHA-1 hash of <code>m1 + m2 + m3</code> + */ + public static byte[] hash(byte[] m1, byte[] m2, byte[] m3) { + digest.update(m1, 0, m1.length); + digest.update(m2, 0, m2.length); + digest.update(m3, 0, m3.length); + byte[] result = new byte[HASH_LEN]; + digest.doFinal(result, 0); + return(result); + } + /** + * Return <code>n</code> "random" octets generated based on key <code>k</code>. + * + * Generates <code>n</code> "random" octets or a key stream based on + * <code>k</code>. The stream is generated by using AES in counter mode + * with key <code>k</code>. + * + * @param k the key for AES counter mode + * @param n number of octets requested + * @return a keystream of n octets + * @throws Error if <code>k</code> is not KEY_LEN (=16) octets long. + */ + public static byte[] prng(byte[] k, int n) { + byte[] result = new byte[ n ]; + byte[] block = new byte[ KEY_LEN ]; + byte[] encrypted = new byte[ KEY_LEN ]; + if (k.length != KEY_LEN) + throw new Error("Argument k to encrypt must be KEY_LEN bytes"); + KeyParameter params = new KeyParameter(k); + aes_cipher.init(true, params); + for (int i=0, p=0; i<n; p++) { + block[block.length - 1] = (byte) (p & 0xff); + block[block.length - 2] = (byte) ((p >> 8) & 0xff); + block[block.length - 3] = (byte) ((p >> 16) & 0xff); + block[block.length - 4] = (byte) ((p >> 24) & 0xff); + + aes_cipher.processBlock(block, 0, encrypted, 0); + for (int j=0; j<encrypted.length && i<n; j++, i++) + result[i] = encrypted[j]; + }; + return(result); + } + /** + * Encrypt a message <code>m</code> using AES counter mode with key <code>k</code>. + * + * Returns the result of xor(m, prng(k, len(m))), i.e. the message <code>m</code> + * encrypted using AES in counter mode with key <code>k</code>. + * + * @param k the key for AES counter mode + * @param m the message to encrypt + * @return the encrypted message. (len(e) == len(m)) + * @throws Error if <code>k</code> is not KEY_LEN (=16) octets long. + */ + public static byte[] encrypt(byte[] k, byte[] m) { + byte[] result; + if (k.length != KEY_LEN) + throw new Error("Argument k to encrypt must be KEY_LEN bytes"); + result = xor(m, prng(k, m.length)); + return(result); + } + /** + * Encrypt a message using our super pseudorandom permutation. + * + * Encrypt message <code>m</code> using an instance of the LIONESS + * super pseudorandom permutation (SPRP). + * + * This SPRP has the property that any change in the encrypted value + * will make the decryption look like random bits + * + * @param k the key for our SPRP function + * @param m the message to encrypt + * @return the encrypted message. (len(e) == len(m)) + * @throws Error if <code>k</code> is not SPRP_KEY_LEN (=20) octets long + * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets + */ + public static byte[] sprpEncrypt(byte[] k, byte[] m) { + if (m.length < HASH_LEN) + throw new Error("Argument m to sprp_encrypt must be of at least HASH_LEN bytes"); + if (k.length != SPRP_KEY_LEN) + throw new Error("Argument k to sprp_encrypt must be SPRP_KEY_LEN bytes"); + byte[] result; + byte[] k1 = new byte[HASH_LEN]; + byte[] k2 = new byte[HASH_LEN]; + byte[] k3 = new byte[HASH_LEN]; + byte[] k4 = new byte[HASH_LEN]; + + System.arraycopy(k, 0, k1, 0, k.length); + System.arraycopy(k, 0, k2, 0, k.length); + System.arraycopy(k, 0, k3, 0, k.length); + System.arraycopy(k, 0, k4, 0, k.length); + k2[19] = (byte) (k1[19] ^ 0x01); + k3[19] = (byte) (k1[19] ^ 0x02); + k4[19] = (byte) (k1[19] ^ 0x03); + byte[] l = Util.slice(m, 0, HASH_LEN); + byte[] r = Util.slice(m, HASH_LEN, m.length-HASH_LEN); + r = encrypt( Util.slice(hash( k1, l, k1), 0, KEY_LEN), r); + l = xor( l, hash(k2, r, k2 )); + r = encrypt( Util.slice(hash( k3, l, k3), 0, KEY_LEN), r); + l = xor( l, hash(k4, r, k4 )); + result = Util.concat(l, r); + return(result); + } + /** + * Encrypt a message using our super pseudorandom permutation. + * + * The message will be encrypted using a subkey constructed from + * <code>k</code> and <code>p</code> (=HASH(k | p)). + * + * @param k a master key for the SPRP encryption. + * @param p a string to build a subkey with. + * @param m the message to encrypt + * @return the encrypted message. (len(e) == len(m)) + * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets + * @see #sprpEncrypt(byte[] k, byte[] m) + */ + public static byte[] sprpEncrypt(byte[] k, String p, byte[] m) { + return sprpEncrypt(hash(k, Util.toOctets(p)) ,m); + } + /** + * Decrypt a message using our super pseudorandom permutation. + * + * Decrypt message <code>m</code> using our instance of the LIONESS + * super pseudorandom permutation (SPRP). + * + * @param k the key for our SPRP function + * @param m the message to decrypt + * @return the encrypted message. (len(e) == len(m)) + * @throws Error if <code>k</code> is not SPRP_KEY_LEN (=20) octets long + * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets + * @see #sprpEncrypt(byte[] k, byte[] m) + */ + public static byte[] sprpDecrypt(byte[] k, byte[] m) { + if (m.length < HASH_LEN) + throw new Error("Argument m to sprp_decrypt must be of at least HASH_LEN bytes"); + if (k.length != SPRP_KEY_LEN) + throw new Error("Argument k to sprp_decrypt must be SPRP_KEY_LEN bytes"); + byte[] result; + byte[] k1 = new byte[HASH_LEN]; + byte[] k2 = new byte[HASH_LEN]; + byte[] k3 = new byte[HASH_LEN]; + byte[] k4 = new byte[HASH_LEN]; + + System.arraycopy(k, 0, k1, 0, k.length); + System.arraycopy(k, 0, k2, 0, k.length); + System.arraycopy(k, 0, k3, 0, k.length); + System.arraycopy(k, 0, k4, 0, k.length); + k2[19] = (byte) (k1[19] ^ 0x01); + k3[19] = (byte) (k1[19] ^ 0x02); + k4[19] = (byte) (k1[19] ^ 0x03); + byte[] l = Util.slice(m, 0, HASH_LEN); + byte[] r = Util.slice(m, HASH_LEN, m.length-HASH_LEN); + l = xor( l, hash(k4, r, k4 )); + r = encrypt( Util.slice(hash( k3, l, k3), 0, KEY_LEN), r); + l = xor( l, hash(k2, r, k2 )); + r = encrypt( Util.slice(hash( k1, l, k1), 0, KEY_LEN), r); + result = Util.concat(l, r); + return(result); + } + /** + * Decrypt a message using our super pseudorandom permutation. + * + * The message will be decrypted using a subkey constructed from + * <code>k</code> and <code>p</code> (=HASH(k | p)). + * + * @param k a master key for the SPRP decryption. + * @param p a string to build a subkey with. + * @param m the message to decrypt + * @return the encrypted message. (len(e) == len(m)) + * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets + * @see #sprpEncrypt(byte[] k, byte[] m) + * @see #sprpDecrypt(byte[] k, byte[] m) + */ + public static byte[] sprpDecrypt(byte[] k, String p, byte[] m) { + return sprpDecrypt(hash(k, Util.toOctets(p)) ,m); + } + /** + * Build a subkey from <code>k</code> and <code>p</code>. + * + * The subkey is generated using HASH(k | p). + * + * @param k a master key + * @param p a string to build a subkey with + * @return the subkey + * @throws Error if <code>k</code> is not KEY_LEN (=16) octets long + */ + public static byte[] subKey(byte[] k, String p) { + if (k.length != KEY_LEN) + throw new Error("Argument k to subKey must be KEY_LEN bytes"); + return Util.slice(hash(k, Util.toOctets(p)), 0, 16); + } +} diff --git a/src/org/noreply/fancydress/crypto/RSAPublicKey.java b/src/org/noreply/fancydress/crypto/RSAPublicKey.java new file mode 100644 index 0000000..13f8b53 --- /dev/null +++ b/src/org/noreply/fancydress/crypto/RSAPublicKey.java @@ -0,0 +1,125 @@ +package org.noreply.fancydress.crypto; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERInputStream; +import org.bouncycastle.asn1.x509.RSAPublicKeyStructure; +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.RSAEngine; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.encodings.OAEPEncoding; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.noreply.fancydress.misc.Util; + +/** + * This class is a wrapper for the bouncycastle RSA functionality. + */ +public class RSAPublicKey { + /** + * The overhead (in octets) added by OAEP padding. + */ + public static final int PK_OVERHEAD_LEN = 42; + /** + * The length of encrypted data. (we use 2048 bit keys) + */ + public static final int PK_ENC_LEN = 256; + /** + * The maximum amount of octets that can be encoded in an RSA message. + */ + public static final int PK_MAX_DATA_LEN = PK_ENC_LEN - PK_OVERHEAD_LEN; + + /** + * the RSA cipher from bouncycastle. + */ + private AsymmetricBlockCipher rsa_cipher; + /** + * a key structure + */ + private RSAPublicKeyStructure key; + /** + * key parameters + */ + private RSAKeyParameters keyParams; + + /** + * Construct an instance of an RSAPublicKey from the ASN1 encoded key. + * + * @param asnEncoded ASN1 encoded RSA key. + */ + public RSAPublicKey(byte[] asnEncoded) { + String p = new String("He who would make his own liberty secure, must guard even his enemy from oppression."); + rsa_cipher = new OAEPEncoding(new RSAEngine(), new SHA1Digest(), CryptoPrimitives.hash(Util.toOctets(p))); + + ByteArrayInputStream bIn = new ByteArrayInputStream(asnEncoded); + DERInputStream dIn = new DERInputStream(bIn); + try { + key = new RSAPublicKeyStructure((ASN1Sequence)dIn.readObject()); + } catch (IOException e) { + throw new Error(e); + } + keyParams = new RSAKeyParameters(false, key.getModulus(), key.getPublicExponent()); + }; + /** + * Encrypt a message. + * + * Encrypts message m to this key. + * + * @param m the message to encrypt. + * @return the encrypted message (PK_ENC_LEN octets long) + */ + public byte[] encrypt(byte[] m) { + rsa_cipher.init(true, keyParams); + byte[] result; + try { + result = rsa_cipher.processBlock(m, 0, m.length); + } catch (InvalidCipherTextException e) { + throw new Error(e); + } + return(result); + } + /** + * Verify a signature. + * + * Verify that the information signed in signature <code>sig</code> + * matches the value in <code>m</code>. + * + * @param sig the signature + * @param m message to compare the signed information to + * @return true if the signature is valid + * @throws InvalidCipherTextException + */ + public boolean verify(byte[] sig, byte[] m) throws InvalidCipherTextException { + rsa_cipher.init(false, keyParams); + byte[] signedDigest = rsa_cipher.processBlock(sig, 0, sig.length); + byte[] digest = CryptoPrimitives.hash(m); + boolean verifies = Util.equal(digest, signedDigest); + return(verifies); + } + /** + * Return the ASN 1 encoding of this key. + * + * @return this key, asn1 encoded + */ + public byte[] encode() { + DEROctetString asn = new DEROctetString(key); + byte[] result = asn.getOctets(); + return(result); + } + /** + * Get this key's fingerprint. + * + * The fingerprint (or keyid) is the hash of the asn1 representation of this key. + * + * @return the fingerprint. + */ + public byte[] getFingerprint() { + DEROctetString asn = new DEROctetString(key); + byte[] encoded = asn.getOctets(); + byte[] result = CryptoPrimitives.hash(encoded); + return(result); + } +} |