summaryrefslogtreecommitdiff
path: root/src/org/noreply/fancydress/crypto
diff options
context:
space:
mode:
authorPeter Palfrader <peter@palfrader.org>2003-10-09 11:41:45 +0000
committerPeter Palfrader <peter@palfrader.org>2003-10-09 11:41:45 +0000
commit566d17f731637df6828bdf32502a0fb123882dbe (patch)
treefc09fdfb90953134fa1d25f73367307502348a22 /src/org/noreply/fancydress/crypto
parent018eea460ee32df1b70c40c2eca05f06c06daca5 (diff)
Initial import
Diffstat (limited to 'src/org/noreply/fancydress/crypto')
-rw-r--r--src/org/noreply/fancydress/crypto/CryptoPrimitives.java299
-rw-r--r--src/org/noreply/fancydress/crypto/RSAPublicKey.java125
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);
+ }
+}