Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Hook <dgh@cryptoworkshop.com>2014-04-26 11:25:42 +0400
committerDavid Hook <dgh@cryptoworkshop.com>2014-04-26 11:25:42 +0400
commitfb77cfd88054b6144b6ce6e3a544fca00668d11a (patch)
treeb9f111ee11870330d00f56f131642277edd2b634 /core/src/main/java/org/bouncycastle/crypto
parent4628c4139cdbcae3f3bfabb60e352842a24286b9 (diff)
initial cut of lightweight RFC5649 wrapping
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto')
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/engines/AESWrapPadEngine.java10
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java294
2 files changed, 304 insertions, 0 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/AESWrapPadEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/AESWrapPadEngine.java
new file mode 100644
index 00000000..77760612
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/engines/AESWrapPadEngine.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.crypto.engines;
+
+public class AESWrapPadEngine
+ extends RFC5649WrapEngine
+{
+ public AESWrapPadEngine()
+ {
+ super(new AESEngine());
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java
new file mode 100644
index 00000000..548969ac
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java
@@ -0,0 +1,294 @@
+package org.bouncycastle.crypto.engines;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Pack;
+
+/**
+ * An implementation of the AES Key Wrap with Padding specification
+ * as described in RFC 5649.
+ * <p>
+ * For details on the specification see:
+ * <a href="https://tools.ietf.org/html/rfc5649">https://tools.ietf.org/html/rfc5649</a>
+ * </p>
+ */
+public class RFC5649WrapEngine
+ implements Wrapper
+{
+ private BlockCipher engine;
+ private KeyParameter param;
+ private boolean forWrapping;
+
+ // The AIV as defined in the RFC
+ private byte[] highOrderIV = {(byte)0xa6, (byte)0x59, (byte)0x59, (byte)0xa6};
+ private byte[] preIV = highOrderIV;
+
+ private byte[] extractedAIV = null;
+
+ public RFC5649WrapEngine(BlockCipher engine)
+ {
+ this.engine = engine;
+ }
+
+ public void init(boolean forWrapping, CipherParameters param)
+ {
+ this.forWrapping = forWrapping;
+
+ if (param instanceof ParametersWithRandom)
+ {
+ param = ((ParametersWithRandom)param).getParameters();
+ }
+
+ if (param instanceof KeyParameter)
+ {
+ this.param = (KeyParameter)param;
+ }
+ else if (param instanceof ParametersWithIV)
+ {
+ this.preIV = ((ParametersWithIV)param).getIV();
+ this.param = (KeyParameter)((ParametersWithIV)param).getParameters();
+ if (this.preIV.length != 4)
+ {
+ throw new IllegalArgumentException("IV length not equal to 4");
+ }
+ }
+ }
+
+ public String getAlgorithmName()
+ {
+ return engine.getAlgorithmName();
+ }
+
+ /**
+ * Pads the plaintext (i.e., the key to be wrapped)
+ * as per section 4.1 of RFC 5649.
+ *
+ * @param plaintext The key being wrapped.
+ * @return The padded key.
+ */
+ private byte[] padPlaintext(byte[] plaintext)
+ {
+ int plaintextLength = plaintext.length;
+ int numOfZerosToAppend = (8 - (plaintextLength % 8)) % 8;
+ byte[] paddedPlaintext = new byte[plaintextLength + numOfZerosToAppend];
+ System.arraycopy(plaintext, 0, paddedPlaintext, 0, plaintextLength);
+ if (numOfZerosToAppend != 0)
+ {
+ // plaintext (i.e., key to be wrapped) does not have
+ // a multiple of 8 octet blocks so it must be padded
+ byte[] zeros = new byte[numOfZerosToAppend];
+ System.arraycopy(zeros, 0, paddedPlaintext, plaintextLength, numOfZerosToAppend);
+ }
+ return paddedPlaintext;
+ }
+
+ public byte[] wrap(byte[] in, int inOff, int inLen)
+ {
+ if (!forWrapping)
+ {
+ throw new IllegalStateException("not set for wrapping");
+ }
+ byte[] iv = new byte[8];
+
+ // MLI = size of key to be wrapped
+ byte[] mli = Pack.intToBigEndian(inLen);
+ // copy in the fixed portion of the AIV
+ System.arraycopy(preIV, 0, iv, 0, preIV.length);
+ // copy in the MLI after the AIV
+ System.arraycopy(mli, 0, iv, preIV.length, mli.length);
+
+ // get the relevant plaintext to be wrapped
+ byte[] relevantPlaintext = new byte[inLen];
+ System.arraycopy(in, inOff, relevantPlaintext, 0, inLen);
+ byte[] paddedPlaintext = padPlaintext(relevantPlaintext);
+
+ if (paddedPlaintext.length == 8)
+ {
+ // if the padded plaintext contains exactly 8 octets,
+ // then prepend iv and encrypt using AES in ECB mode.
+
+ // prepend the IV to the plaintext
+ byte[] paddedPlainTextWithIV = new byte[paddedPlaintext.length + iv.length];
+ System.arraycopy(iv, 0, paddedPlainTextWithIV, 0, iv.length);
+ System.arraycopy(paddedPlaintext, 0, paddedPlainTextWithIV, iv.length, paddedPlaintext.length);
+
+ engine.init(true, param);
+ for (int i = 0; i < paddedPlainTextWithIV.length; i += engine.getBlockSize())
+ {
+ engine.processBlock(paddedPlainTextWithIV, i, paddedPlainTextWithIV, i);
+ }
+
+ return paddedPlainTextWithIV;
+ }
+ else
+ {
+ // otherwise, apply the RFC 3394 wrap to
+ // the padded plaintext with the new IV
+ Wrapper wrapper = new RFC3394WrapEngine(engine);
+ ParametersWithIV paramsWithIV = new ParametersWithIV(param, iv);
+ wrapper.init(true, paramsWithIV);
+ return wrapper.wrap(paddedPlaintext, inOff, paddedPlaintext.length);
+ }
+
+ }
+
+ public byte[] unwrap(byte[] in, int inOff, int inLen)
+ throws InvalidCipherTextException
+ {
+ if (forWrapping)
+ {
+ throw new IllegalStateException("not set for unwrapping");
+ }
+
+ int n = inLen / 8;
+
+ if ((n * 8) != inLen)
+ {
+ throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes");
+ }
+
+ if (n == 1)
+ {
+ throw new InvalidCipherTextException("unwrap data must be at least 16 bytes");
+ }
+
+ byte[] relevantCiphertext = new byte[inLen];
+ System.arraycopy(in, inOff, relevantCiphertext, 0, inLen);
+ byte[] decrypted = new byte[inLen];
+ byte[] paddedPlaintext;
+
+ if (n == 2)
+ {
+ // When there are exactly two 64-bit blocks of ciphertext,
+ // they are decrypted as a single block using AES in ECB.
+ engine.init(false, param);
+ for (int i = 0; i < relevantCiphertext.length; i += engine.getBlockSize())
+ {
+ engine.processBlock(relevantCiphertext, i, decrypted, i);
+ }
+
+ // extract the AIV
+ extractedAIV = new byte[8];
+ System.arraycopy(decrypted, 0, extractedAIV, 0, extractedAIV.length);
+ paddedPlaintext = new byte[decrypted.length - extractedAIV.length];
+ System.arraycopy(decrypted, extractedAIV.length, paddedPlaintext, 0, paddedPlaintext.length);
+ }
+ else
+ {
+ // Otherwise, unwrap as per RFC 3394 but don't check IV the same way
+ decrypted = rfc3394UnwrapNoIvCheck(in, inOff, inLen);
+ paddedPlaintext = decrypted;
+ }
+
+ // Decompose the extracted AIV to the fixed portion and the MLI
+ byte[] extractedHighOrderAIV = new byte[4];
+ byte[] mliBytes = new byte[4];
+ System.arraycopy(extractedAIV, 0, extractedHighOrderAIV, 0, extractedHighOrderAIV.length);
+ System.arraycopy(extractedAIV, extractedHighOrderAIV.length, mliBytes, 0, mliBytes.length);
+ int mli = Pack.bigEndianToInt(mliBytes, 0);
+
+ // Even if a check fails we still continue and check everything
+ // else in order to avoid certain timing based side-channel attacks.
+ boolean isValid = true;
+
+ // Check the fixed portion of the AIV
+ if (!Arrays.constantTimeAreEqual(extractedHighOrderAIV, preIV))
+ {
+ isValid = false;
+ }
+
+ // Check the MLI against the actual length
+ int upperBound = paddedPlaintext.length;
+ int lowerBound = upperBound - 8;
+ if (mli <= lowerBound)
+ {
+ isValid = false;
+ }
+ if (mli > upperBound)
+ {
+ isValid = false;
+ }
+
+ // Check the number of padded zeros
+ int expectedZeros = upperBound - mli;
+ byte[] zeros = new byte[expectedZeros];
+ byte[] pad = new byte[expectedZeros];
+ System.arraycopy(paddedPlaintext, paddedPlaintext.length - expectedZeros, pad, 0, expectedZeros);
+ if (!Arrays.constantTimeAreEqual(pad, zeros))
+ {
+ isValid = false;
+ }
+
+ // Extract the plaintext from the padded plaintext
+ byte[] plaintext = new byte[mli];
+ System.arraycopy(paddedPlaintext, 0, plaintext, 0, plaintext.length);
+
+ if (!isValid)
+ {
+ throw new InvalidCipherTextException("checksum failed");
+ }
+
+ return plaintext;
+ }
+
+ /**
+ * Performs steps 1 and 2 of the unwrap process defined in RFC 3394.
+ * This code is duplicated from RFC3394WrapEngine because that class
+ * will throw an error during unwrap because the IV won't match up.
+ *
+ * @param in
+ * @param inOff
+ * @param inLen
+ * @return Unwrapped data.
+ */
+ private byte[] rfc3394UnwrapNoIvCheck(byte[] in, int inOff, int inLen)
+ {
+ byte[] iv = new byte[8];
+ byte[] block = new byte[inLen - iv.length];
+ byte[] a = new byte[iv.length];
+ byte[] buf = new byte[8 + iv.length];
+
+ System.arraycopy(in, inOff, a, 0, iv.length);
+ System.arraycopy(in, inOff + iv.length, block, 0, inLen - iv.length);
+
+ engine.init(false, param);
+
+ int n = inLen / 8;
+ n = n - 1;
+
+ for (int j = 5; j >= 0; j--)
+ {
+ for (int i = n; i >= 1; i--)
+ {
+ System.arraycopy(a, 0, buf, 0, iv.length);
+ System.arraycopy(block, 8 * (i - 1), buf, iv.length, 8);
+
+ int t = n * j + i;
+ for (int k = 1; t != 0; k++)
+ {
+ byte v = (byte)t;
+
+ buf[iv.length - k] ^= v;
+
+ t >>>= 8;
+ }
+
+ engine.processBlock(buf, 0, buf, 0);
+ System.arraycopy(buf, 0, a, 0, 8);
+ System.arraycopy(buf, 8, block, 8 * (i - 1), 8);
+ }
+ }
+
+ // set the extracted AIV
+ extractedAIV = a;
+
+ return block;
+ }
+
+}