diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index 3285296bc3..23d72a3a62 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -663,6 +663,8 @@
+
+
diff --git a/crypto/src/crypto/engines/AesWrapPadEngine.cs b/crypto/src/crypto/engines/AesWrapPadEngine.cs
new file mode 100644
index 0000000000..d03d48838c
--- /dev/null
+++ b/crypto/src/crypto/engines/AesWrapPadEngine.cs
@@ -0,0 +1,10 @@
+namespace Org.BouncyCastle.Crypto.Engines
+{
+ public class AesWrapPadEngine : Rfc5649WrapEngine
+ {
+ public AesWrapPadEngine()
+ : base(new AesEngine())
+ {
+ }
+ }
+}
diff --git a/crypto/src/crypto/engines/Rfc5649WrapEngine.cs b/crypto/src/crypto/engines/Rfc5649WrapEngine.cs
new file mode 100644
index 0000000000..4b2c2fc986
--- /dev/null
+++ b/crypto/src/crypto/engines/Rfc5649WrapEngine.cs
@@ -0,0 +1,283 @@
+using System;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Engines
+{
+ ///
+ /// An implementation of the AES Key Wrap with Padding specification as described in RFC 5649.
+ /// For details on the specification see https://tools.ietf.org/html/rfc5649.
+ ///
+ public class Rfc5649WrapEngine : IWrapper
+ {
+ private readonly IBlockCipher engine;
+ private KeyParameter param;
+ private bool forWrapping;
+
+ // The AIV as defined in the RFC
+ private static byte[] highOrderIV = { 0xa6, 0x59, 0x59, 0xa6 };
+ private byte[] preIV = highOrderIV;
+
+ private byte[] extractedAIV = null;
+
+ public virtual string AlgorithmName => engine.AlgorithmName;
+
+ public Rfc5649WrapEngine(IBlockCipher engine)
+ {
+ this.engine = engine;
+ }
+
+ public void Init(bool forWrapping, ICipherParameters param)
+ {
+ this.forWrapping = forWrapping;
+
+ if (param is ParametersWithRandom)
+ {
+ param = ((ParametersWithRandom)param).Parameters;
+ }
+
+ if (param is KeyParameter)
+ {
+ this.param = (KeyParameter)param;
+ this.preIV = highOrderIV;
+ }
+ else if (param is ParametersWithIV)
+ {
+ this.preIV = ((ParametersWithIV)param).GetIV();
+ this.param = (KeyParameter)((ParametersWithIV)param).Parameters;
+ if (this.preIV.Length != 4)
+ {
+ throw new ArgumentException("IV length not equal to 4", nameof(param));
+ }
+ }
+ }
+
+ ///
+ /// Pads the plaintext (i.e., the key to be wrapped) as per section 4.1 of RFC 5649.
+ ///
+ /// The key being wrapped.
+ /// The padded key.
+ private byte[] PadPlaintext(byte[] plaintext)
+ {
+ int plaintextLength = plaintext.Length;
+ int numOfZerosToAppend = (8 - (plaintextLength % 8)) % 8;
+ byte[] paddedPlaintext = new byte[plaintextLength + numOfZerosToAppend];
+ Array.Copy(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];
+ Array.Copy(zeros, 0, paddedPlaintext, plaintextLength, numOfZerosToAppend);
+ }
+ return paddedPlaintext;
+ }
+
+ public byte[] Wrap(byte[] input, int inOff, int inLen)
+ {
+ if (!forWrapping)
+ {
+ throw new InvalidOperationException("not set for wrapping");
+ }
+ byte[] iv = new byte[8];
+
+ // MLI = size of key to be wrapped
+ byte[] mli = Pack.UInt32_To_BE((uint)inLen);
+ // copy in the fixed portion of the AIV
+ Array.Copy(preIV, 0, iv, 0, preIV.Length);
+ // copy in the MLI after the AIV
+ Array.Copy(mli, 0, iv, preIV.Length, mli.Length);
+
+ // get the relevant plaintext to be wrapped
+ byte[] relevantPlaintext = new byte[inLen];
+ Array.Copy(input, 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];
+ Array.Copy(iv, 0, paddedPlainTextWithIV, 0, iv.Length);
+ Array.Copy(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
+ IWrapper wrapper = new Rfc3394WrapEngine(engine);
+ ParametersWithIV paramsWithIV = new ParametersWithIV(param, iv);
+ wrapper.Init(true, paramsWithIV);
+ return wrapper.Wrap(paddedPlaintext, 0, paddedPlaintext.Length);
+ }
+
+ }
+
+ public byte[] Unwrap(byte[] input, int inOff, int inLen)
+ {
+ if (forWrapping)
+ {
+ throw new InvalidOperationException("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];
+ Array.Copy(input, 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];
+ Array.Copy(decrypted, 0, extractedAIV, 0, extractedAIV.Length);
+ paddedPlaintext = new byte[decrypted.Length - extractedAIV.Length];
+ Array.Copy(decrypted, extractedAIV.Length, paddedPlaintext, 0, paddedPlaintext.Length);
+ }
+ else
+ {
+ // Otherwise, unwrap as per RFC 3394 but don't check IV the same way
+ decrypted = Rfc3394UnwrapNoIvCheck(input, 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];
+ Array.Copy(extractedAIV, 0, extractedHighOrderAIV, 0, extractedHighOrderAIV.Length);
+ Array.Copy(extractedAIV, extractedHighOrderAIV.Length, mliBytes, 0, mliBytes.Length);
+ var mli = Pack.BE_To_UInt32(mliBytes, 0);
+ // Even if a check fails we still continue and check everything
+ // else in order to avoid certain timing based side-channel attacks.
+ var 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
+ var expectedZeros = upperBound - mli;
+ if (expectedZeros >= paddedPlaintext.Length)
+ {
+ isValid = false;
+ expectedZeros = paddedPlaintext.Length;
+ }
+
+ byte[] zeros = new byte[expectedZeros];
+ byte[] pad = new byte[expectedZeros];
+ Array.Copy(paddedPlaintext, paddedPlaintext.Length - expectedZeros, pad, 0, expectedZeros);
+ if (!Arrays.ConstantTimeAreEqual(pad, zeros))
+ {
+ isValid = false;
+ }
+
+ if (!isValid)
+ {
+ throw new InvalidCipherTextException("checksum failed");
+ }
+
+ // Extract the plaintext from the padded plaintext
+ byte[] plaintext = new byte[mli];
+ Array.Copy(paddedPlaintext, 0, plaintext, 0, plaintext.Length);
+
+ 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.
+ ///
+ ///
+ ///
+ ///
+ /// Unwrapped data.
+ private byte[] Rfc3394UnwrapNoIvCheck(byte[] input, 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];
+
+ Array.Copy(input, inOff, a, 0, iv.Length);
+ Array.Copy(input, 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--)
+ {
+ Array.Copy(a, 0, buf, 0, iv.Length);
+ Array.Copy(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 = (int)((uint)t >> 8);
+ }
+
+ engine.ProcessBlock(buf, 0, buf, 0);
+ Array.Copy(buf, 0, a, 0, 8);
+ Array.Copy(buf, 8, block, 8 * (i - 1), 8);
+ }
+ }
+
+ // set the extracted AIV
+ extractedAIV = a;
+
+ return block;
+ }
+ }
+}
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index 5b39de6adc..ad08abc126 100644
--- a/crypto/test/UnitTests.csproj
+++ b/crypto/test/UnitTests.csproj
@@ -158,6 +158,7 @@
+
diff --git a/crypto/test/src/crypto/test/AESWrapPadTest.cs b/crypto/test/src/crypto/test/AESWrapPadTest.cs
new file mode 100644
index 0000000000..4546234fdf
--- /dev/null
+++ b/crypto/test/src/crypto/test/AESWrapPadTest.cs
@@ -0,0 +1,244 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+ [TestFixture]
+ class AesWrapPadTest : ITest
+ {
+ public string Name
+ {
+ get
+ {
+ return "AESWrapPad";
+ }
+ }
+
+ private ITestResult WrapTest(
+ int id,
+ byte[] kek,
+ byte[] inBytes,
+ byte[] outBytes)
+ {
+ IWrapper wrapper = new AesWrapPadEngine();
+
+ wrapper.Init(true, new KeyParameter(kek));
+
+ try
+ {
+ byte[] cText = wrapper.Wrap(inBytes, 0, inBytes.Length);
+ if (!Arrays.AreEqual(cText, outBytes))
+ {
+ return new SimpleTestResult(false, Name + ": failed wrap test " + id
+ + " expected " + Hex.ToHexString(outBytes)
+ + " got " + Hex.ToHexString(cText));
+ }
+ }
+ catch (Exception e)
+ {
+ return new SimpleTestResult(false, Name + ": failed wrap test exception " + e);
+ }
+
+ wrapper.Init(false, new KeyParameter(kek));
+
+ try
+ {
+ byte[] pText = wrapper.Unwrap(outBytes, 0, outBytes.Length);
+ if (!Arrays.AreEqual(pText, inBytes))
+ {
+ return new SimpleTestResult(false, Name + ": failed unwrap test " + id
+ + " expected " + Hex.ToHexString(inBytes)
+ + " got " + Hex.ToHexString(pText));
+ }
+ }
+ catch (Exception e)
+ {
+ return new SimpleTestResult(false, Name + ": failed unwrap test exception.", e);
+ }
+
+ return new SimpleTestResult(true, Name + ": Okay");
+ }
+
+ public ITestResult Perform()
+ {
+ // Message length is divided by 8
+
+ byte[] kek1 = Hex.Decode("000102030405060708090a0b0c0d0e0f");
+ byte[] in1 = Hex.Decode("00112233445566778899aabbccddeeff");
+ byte[] out1 = Hex.Decode("2cef0c9e30de26016c230cb78bc60d51b1fe083ba0c79cd5");
+ ITestResult result = WrapTest(1, kek1, in1, out1);
+
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek2 = Hex.Decode("000102030405060708090a0b0c0d0e0f1011121314151617");
+ byte[] in2 = Hex.Decode("00112233445566778899aabbccddeeff");
+ byte[] out2 = Hex.Decode("5fd7477fdc165910c8e5dd891a421b10db10362fd293b128");
+ result = WrapTest(2, kek2, in2, out2);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek3 = Hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
+ byte[] in3 = Hex.Decode("00112233445566778899aabbccddeeff");
+ byte[] out3 = Hex.Decode("afc860015ffe2d75bedf43c444fe58f4ad9d89c4ec71e23b");
+ result = WrapTest(3, kek3, in3, out3);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek4 = Hex.Decode("000102030405060708090a0b0c0d0e0f1011121314151617");
+ byte[] in4 = Hex.Decode("00112233445566778899aabbccddeeff0001020304050607");
+ byte[] out4 = Hex.Decode("39c3bf03c71e0d49bd968f26397b3855e5e89eaafd256edbc2f1d03f3266f3f4");
+ result = WrapTest(4, kek4, in4, out4);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek5 = Hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
+ byte[] in5 = Hex.Decode("00112233445566778899aabbccddeeff0001020304050607");
+ byte[] out5 = Hex.Decode("b9f05286f13fc80d1f8614a1acac931f293f66d7a3bb3811fb568f7108ec6210");
+ result = WrapTest(5, kek5, in5, out5);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek6 = Hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
+ byte[] in6 = Hex.Decode("00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f");
+ byte[] out6 = Hex.Decode("4a8029243027353b0694cf1bd8fc745bb0ce8a739b19b1960b12426d4c39cfeda926d103ab34e9f6");
+ result = WrapTest(6, kek6, in6, out6);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ // Message length is NOT divided by 8 (will be padded)
+
+ byte[] kek7 = Hex.Decode("000102030405060708090a0b0c0d0e0f");
+ byte[] in7 = Hex.Decode("00112233");
+ byte[] out7 = Hex.Decode("9475a703b9ed66e4898e8154e66273a7");
+ result = WrapTest(7, kek7, in7, out7);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek8 = Hex.Decode("000102030405060708090a0b0c0d0e0f1011121314151617");
+ byte[] in8 = Hex.Decode("00112233445566778899");
+ byte[] out8 = Hex.Decode("62a641a96427fde579e81d6b9a9ea4fc9585d56736e3b74f");
+ result = WrapTest(8, kek8, in8, out8);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek9 = Hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
+ byte[] in9 = Hex.Decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f001122334455");
+ byte[] out9 = Hex.Decode("3b4d9aa29078180ccfe9c0b8b0775408a071ebf3d58842d8f14b26f55aa4e40e24d138b84023c7c24f2a065f853a59a5");
+ result = WrapTest(9, kek9, in9, out9);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ // RFC 5649 test vectors
+ byte[] kek10 = Hex.Decode("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8");
+ byte[] in10 = Hex.Decode("c37b7e6492584340bed12207808941155068f738");
+ byte[] out10 = Hex.Decode("138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a");
+ result = WrapTest(10, kek10, in10, out10);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ byte[] kek11 = Hex.Decode("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8");
+ byte[] in11 = Hex.Decode("466f7250617369");
+ byte[] out11 = Hex.Decode("afbeb0f07dfbf5419200f2ccb50bb24f");
+ result = WrapTest(11, kek11, in11, out11);
+ if (!result.IsSuccessful())
+ {
+ return result;
+ }
+
+ IWrapper wrapper = new AesWrapPadEngine();
+ KeyParameter key = new KeyParameter(new byte[16]);
+ byte[] buf = new byte[16];
+
+ try
+ {
+ wrapper.Init(true, key);
+
+ wrapper.Unwrap(buf, 0, buf.Length);
+
+ return new SimpleTestResult(false, Name + ": failed unwrap state test.");
+ }
+ catch (InvalidOperationException)
+ {
+ // expected
+ }
+ catch (InvalidCipherTextException e)
+ {
+ return new SimpleTestResult(false, Name + ": unexpected exception: " + e, e);
+ }
+
+ try
+ {
+ wrapper.Init(false, key);
+
+ wrapper.Wrap(buf, 0, buf.Length);
+
+ return new SimpleTestResult(false, Name + ": failed unwrap state test.");
+ }
+ catch (InvalidOperationException)
+ {
+ // expected
+ }
+
+ //
+ // short test
+ //
+ try
+ {
+ wrapper.Init(false, key);
+
+ wrapper.Unwrap(buf, 0, buf.Length / 2);
+
+ return new SimpleTestResult(false, Name + ": failed unwrap short test.");
+ }
+ catch (InvalidCipherTextException)
+ {
+ // expected
+ }
+
+ return new SimpleTestResult(true, Name + ": Okay");
+ }
+
+ public static void Main()
+ {
+ var test = new AesWrapPadTest();
+ ITestResult result = test.Perform();
+
+ Console.WriteLine(result);
+ }
+
+ [Test]
+ public void TestFunction()
+ {
+ string resultText = Perform().ToString();
+
+ Assert.AreEqual(Name + ": Okay", resultText);
+ }
+ }
+}