diff --git a/crypto/src/crypto/encodings/OaepEncoding.cs b/crypto/src/crypto/encodings/OaepEncoding.cs
index 9f5c563c2..287876f12 100644
--- a/crypto/src/crypto/encodings/OaepEncoding.cs
+++ b/crypto/src/crypto/encodings/OaepEncoding.cs
@@ -3,6 +3,7 @@ using System;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Crypto.Encodings
{
@@ -13,7 +14,6 @@ namespace Org.BouncyCastle.Crypto.Encodings
: IAsymmetricBlockCipher
{
private byte[] defHash;
- private IDigest hash;
private IDigest mgf1Hash;
private IAsymmetricBlockCipher engine;
@@ -48,10 +48,11 @@ namespace Org.BouncyCastle.Crypto.Encodings
byte[] encodingParams)
{
this.engine = cipher;
- this.hash = hash;
this.mgf1Hash = mgf1Hash;
this.defHash = new byte[hash.GetDigestSize()];
+ hash.Reset();
+
if (encodingParams != null)
{
hash.BlockUpdate(encodingParams, 0, encodingParams.Length);
@@ -204,28 +205,17 @@ namespace Org.BouncyCastle.Crypto.Encodings
int inLen)
{
byte[] data = engine.ProcessBlock(inBytes, inOff, inLen);
- byte[] block;
+ byte[] block = new byte[engine.GetOutputBlockSize()];
//
// as we may have zeros in our leading bytes for the block we produced
// on encryption, we need to make sure our decrypted block comes back
// the same size.
//
- if (data.Length < engine.GetOutputBlockSize())
- {
- block = new byte[engine.GetOutputBlockSize()];
- Array.Copy(data, 0, block, block.Length - data.Length, data.Length);
- }
- else
- {
- block = data;
- }
+ Array.Copy(data, 0, block, block.Length - data.Length, data.Length);
- if (block.Length < (2 * defHash.Length) + 1)
- {
- throw new InvalidCipherTextException("data too short");
- }
+ bool shortData = (block.Length < (2 * defHash.Length) + 1);
//
// unmask the seed.
@@ -252,36 +242,39 @@ namespace Org.BouncyCastle.Crypto.Encodings
// check the hash of the encoding params.
// long check to try to avoid this been a source of a timing attack.
//
+ bool defHashWrong = false;
+
+ for (int i = 0; i != defHash.Length; i++)
{
- int diff = 0;
- for (int i = 0; i < defHash.Length; ++i)
+ if (defHash[i] != block[defHash.Length + i])
{
- diff |= (byte)(defHash[i] ^ block[defHash.Length + i]);
+ defHashWrong = true;
}
-
- if (diff != 0)
- throw new InvalidCipherTextException("data hash wrong");
}
//
// find the data block
//
- int start;
- for (start = 2 * defHash.Length; start != block.Length; start++)
+ int start = block.Length;
+
+ for (int index = 2 * defHash.Length; index != block.Length; index++)
{
- if (block[start] != 0)
+ if (block[index] != 0 & start == block.Length)
{
- break;
+ start = index;
}
}
- if (start > (block.Length - 1) || block[start] != 1)
- {
- throw new InvalidCipherTextException("data start wrong " + start);
- }
+ bool dataStartWrong = (start > (block.Length - 1) | block[start] != 1);
start++;
+ if (defHashWrong | shortData | dataStartWrong)
+ {
+ Arrays.Fill(block, 0);
+ throw new InvalidCipherTextException("data wrong");
+ }
+
//
// extract the data block
//
@@ -319,9 +312,9 @@ namespace Org.BouncyCastle.Crypto.Encodings
byte[] C = new byte[4];
int counter = 0;
- hash.Reset();
+ mgf1Hash.Reset();
- do
+ while (counter < (length / hashBuf.Length))
{
ItoOSP(counter, C);
@@ -330,8 +323,9 @@ namespace Org.BouncyCastle.Crypto.Encodings
mgf1Hash.DoFinal(hashBuf, 0);
Array.Copy(hashBuf, 0, mask, counter * hashBuf.Length, hashBuf.Length);
+
+ counter++;
}
- while (++counter < (length / hashBuf.Length));
if ((counter * hashBuf.Length) < length)
{
diff --git a/crypto/src/crypto/encodings/Pkcs1Encoding.cs b/crypto/src/crypto/encodings/Pkcs1Encoding.cs
index 35fd96abe..b2d60fe4c 100644
--- a/crypto/src/crypto/encodings/Pkcs1Encoding.cs
+++ b/crypto/src/crypto/encodings/Pkcs1Encoding.cs
@@ -45,16 +45,18 @@ namespace Org.BouncyCastle.Crypto.Encodings
}
- private SecureRandom random;
- private IAsymmetricBlockCipher engine;
- private bool forEncryption;
- private bool forPrivateKey;
- private bool useStrictLength;
- private int pLen = -1;
- private byte[] fallback = null;
+ private SecureRandom random;
+ private IAsymmetricBlockCipher engine;
+ private bool forEncryption;
+ private bool forPrivateKey;
+ private bool useStrictLength;
+ private int pLen = -1;
+ private byte[] fallback = null;
+ private byte[] blockBuffer = null;
/**
* Basic constructor.
+ *
* @param cipher
*/
public Pkcs1Encoding(
@@ -104,9 +106,7 @@ namespace Org.BouncyCastle.Crypto.Encodings
get { return engine.AlgorithmName + "/PKCS1Padding"; }
}
- public void Init(
- bool forEncryption,
- ICipherParameters parameters)
+ public void Init(bool forEncryption, ICipherParameters parameters)
{
AsymmetricKeyParameter kParam;
if (parameters is ParametersWithRandom)
@@ -126,6 +126,10 @@ namespace Org.BouncyCastle.Crypto.Encodings
this.forPrivateKey = kParam.IsPrivate;
this.forEncryption = forEncryption;
+ this.blockBuffer = new byte[engine.GetOutputBlockSize()];
+
+ if (pLen > 0 && fallback == null && random == null)
+ throw new ArgumentException("encoder requires random");
}
public int GetInputBlockSize()
@@ -255,7 +259,6 @@ namespace Org.BouncyCastle.Crypto.Encodings
* @param inLen Length of the encrypted block.
* @param pLen Length of the desired output.
* @return The plaintext without padding, or a random value if the padding was incorrect.
- *
* @throws InvalidCipherTextException
*/
private byte[] DecodeBlockOrRandom(byte[] input, int inOff, int inLen)
@@ -264,7 +267,7 @@ namespace Org.BouncyCastle.Crypto.Encodings
throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing");
byte[] block = engine.ProcessBlock(input, inOff, inLen);
- byte[] random = null;
+ byte[] random;
if (this.fallback == null)
{
random = new byte[this.pLen];
@@ -275,37 +278,25 @@ namespace Org.BouncyCastle.Crypto.Encodings
random = fallback;
}
- /*
- * TODO: This is a potential dangerous side channel. However, you can
- * fix this by changing the RSA engine in a way, that it will always
- * return blocks of the same length and prepend them with 0 bytes if
- * needed.
- */
- if (block.Length < GetOutputBlockSize())
- throw new InvalidCipherTextException("block truncated");
+ byte[] data = (useStrictLength & (block.Length != engine.GetOutputBlockSize())) ? blockBuffer : block;
- /*
- * TODO: Potential side channel. Fix it by making the engine always
- * return blocks of the correct length.
- */
- if (useStrictLength && block.Length != engine.GetOutputBlockSize())
- throw new InvalidCipherTextException("block incorrect size");
-
- /*
- * Check the padding.
- */
- int correct = Pkcs1Encoding.CheckPkcs1Encoding(block, this.pLen);
+ /*
+ * Check the padding.
+ */
+ int correct = CheckPkcs1Encoding(data, this.pLen);
- /*
- * Now, to a constant time constant memory copy of the decrypted value
- * or the random value, depending on the validity of the padding.
- */
+ /*
+ * Now, to a constant time constant memory copy of the decrypted value
+ * or the random value, depending on the validity of the padding.
+ */
byte[] result = new byte[this.pLen];
for (int i = 0; i < this.pLen; i++)
{
- result[i] = (byte)((block[i+(block.Length-pLen)]&(~correct)) | (random[i]&correct));
+ result[i] = (byte)((data[i + (data.Length - pLen)] & (~correct)) | (random[i] & correct));
}
+ Arrays.Fill(data, 0);
+
return result;
}
@@ -327,56 +318,67 @@ namespace Org.BouncyCastle.Crypto.Encodings
}
byte[] block = engine.ProcessBlock(input, inOff, inLen);
+ bool incorrectLength = (useStrictLength & (block.Length != engine.GetOutputBlockSize()));
+ byte[] data;
if (block.Length < GetOutputBlockSize())
{
- throw new InvalidCipherTextException("block truncated");
+ data = blockBuffer;
}
-
- byte type = block[0];
-
- if (type != 1 && type != 2)
+ else
{
- throw new InvalidCipherTextException("unknown block type");
+ data = block;
}
- if (useStrictLength && block.Length != engine.GetOutputBlockSize())
- {
- throw new InvalidCipherTextException("block incorrect size");
- }
+ byte expectedType = (byte)(forPrivateKey ? 2 : 1);
+ byte type = data[0];
+
+ bool badType = (type != expectedType);
//
// find and extract the message block.
//
- int start;
- for (start = 1; start != block.Length; start++)
- {
- byte pad = block[start];
+ int start = FindStart(type, data);
- if (pad == 0)
- {
- break;
- }
+ start++; // data should start at the next byte
- if (type == 1 && pad != (byte)0xff)
- {
- throw new InvalidCipherTextException("block padding incorrect");
- }
+ if (badType | (start < HeaderLength))
+ {
+ Arrays.Fill(data, 0);
+ throw new InvalidCipherTextException("block incorrect");
}
- start++; // data should start at the next byte
-
- if (start > block.Length || start < HeaderLength)
+ // if we get this far, it's likely to be a genuine encoding error
+ if (incorrectLength)
{
- throw new InvalidCipherTextException("no data in block");
+ Arrays.Fill(data, 0);
+ throw new InvalidCipherTextException("block incorrect size");
}
- byte[] result = new byte[block.Length - start];
+ byte[] result = new byte[data.Length - start];
- Array.Copy(block, start, result, 0, result.Length);
+ Array.Copy(data, start, result, 0, result.Length);
return result;
}
- }
+ private int FindStart(byte type, byte[] block)
+ {
+ int start = -1;
+ bool padErr = false;
+
+ for (int i = 1; i != block.Length; i++)
+ {
+ byte pad = block[i];
+
+ if (pad == 0 & start < 0)
+ {
+ start = i;
+ }
+ padErr |= ((type == 1) & (start < 0) & (pad != (byte)0xff));
+ }
+
+ return padErr ? -1 : start;
+ }
+ }
}
diff --git a/crypto/test/src/crypto/test/OAEPTest.cs b/crypto/test/src/crypto/test/OAEPTest.cs
index ee48a183d..bc1dd9292 100644
--- a/crypto/test/src/crypto/test/OAEPTest.cs
+++ b/crypto/test/src/crypto/test/OAEPTest.cs
@@ -5,7 +5,7 @@ using NUnit.Framework;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
@@ -779,6 +779,47 @@ namespace Org.BouncyCastle.Crypto.Tests
OaepVecTest(1027, 4, pubParam, privParam, seed_1027_4, input_1027_4, output_1027_4);
OaepVecTest(1027, 5, pubParam, privParam, seed_1027_5, input_1027_5, output_1027_5);
OaepVecTest(1027, 6, pubParam, privParam, seed_1027_6, input_1027_6, output_1027_6);
+
+ //
+ // OAEP - public encrypt, private decrypt differring hashes
+ //
+ IAsymmetricBlockCipher cipher = new OaepEncoding(new RsaEngine(), new Sha256Digest(), new Sha1Digest(), new byte[10]);
+
+ cipher.Init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
+
+ byte[] input = new byte[10];
+
+ byte[] output = cipher.ProcessBlock(input, 0, input.Length);
+
+ cipher.Init(false, privParam);
+
+ output = cipher.ProcessBlock(output, 0, output.Length);
+
+ for (int i = 0; i != input.Length; i++)
+ {
+ if (output[i] != input[i])
+ {
+ Fail("mixed digest failed decoding");
+ }
+ }
+
+ cipher = new OaepEncoding(new RsaEngine(), new Sha1Digest(), new Sha256Digest(), new byte[10]);
+
+ cipher.Init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
+
+ output = cipher.ProcessBlock(input, 0, input.Length);
+
+ cipher.Init(false, privParam);
+
+ output = cipher.ProcessBlock(output, 0, output.Length);
+
+ for (int i = 0; i != input.Length; i++)
+ {
+ if (output[i] != input[i])
+ {
+ Fail("mixed digest failed decoding");
+ }
+ }
}
public static void Main(
diff --git a/crypto/test/src/crypto/test/RSABlindedTest.cs b/crypto/test/src/crypto/test/RSABlindedTest.cs
index 80d6e8e49..75b9f3a07 100644
--- a/crypto/test/src/crypto/test/RSABlindedTest.cs
+++ b/crypto/test/src/crypto/test/RSABlindedTest.cs
@@ -103,22 +103,22 @@ namespace Org.BouncyCastle.Crypto.Tests
private void doTestTruncatedPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block truncated");
+ checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block incorrect");
}
private void doTestDudPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "unknown block type");
+ checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "block incorrect");
}
private void doTestWrongPaddingPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block padding incorrect");
+ checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block incorrect");
}
private void doTestMissingDataPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "no data in block");
+ checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "block incorrect");
}
private void checkForPkcs1Exception(RsaKeyParameters pubParameters, RsaKeyParameters privParameters, byte[] inputData, string expectedMessage)
diff --git a/crypto/test/src/crypto/test/RsaTest.cs b/crypto/test/src/crypto/test/RsaTest.cs
index e3fc18d02..e9f30cae9 100644
--- a/crypto/test/src/crypto/test/RsaTest.cs
+++ b/crypto/test/src/crypto/test/RsaTest.cs
@@ -54,10 +54,11 @@ namespace Org.BouncyCastle.Crypto.Tests
eng.Init(true, privParameters);
byte[] data = null;
+ byte[] overSized = null;
- try
- {
- data = eng.ProcessBlock(oversizedSig, 0, oversizedSig.Length);
+ try
+ {
+ overSized = data = eng.ProcessBlock(oversizedSig, 0, oversizedSig.Length);
}
catch (Exception e)
{
@@ -70,7 +71,7 @@ namespace Org.BouncyCastle.Crypto.Tests
try
{
- data = eng.ProcessBlock(data, 0, data.Length);
+ data = eng.ProcessBlock(overSized, 0, overSized.Length);
Fail("oversized signature block not recognised");
}
@@ -82,9 +83,22 @@ namespace Org.BouncyCastle.Crypto.Tests
}
}
+ eng = new Pkcs1Encoding(new RsaEngine(), Hex.Decode("feedbeeffeedbeeffeedbeef"));
+ eng.Init(false, new ParametersWithRandom(privParameters, new SecureRandom()));
+
+ try
+ {
+ data = eng.ProcessBlock(overSized, 0, overSized.Length);
+ IsTrue("not fallback", Arrays.AreEqual(Hex.Decode("feedbeeffeedbeeffeedbeef"), data));
+ }
+ catch (InvalidCipherTextException e)
+ {
+ Fail("RSA: failed - exception " + e.ToString(), e);
+ }
- // Create the encoding with StrictLengthEnabled=false (done thru environment in Java version)
- Pkcs1Encoding.StrictLengthEnabled = false;
+
+ // Create the encoding with StrictLengthEnabled=false (done thru environment in Java version)
+ Pkcs1Encoding.StrictLengthEnabled = false;
eng = new Pkcs1Encoding(new RsaEngine());
@@ -92,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Tests
try
{
- data = eng.ProcessBlock(data, 0, data.Length);
+ data = eng.ProcessBlock(overSized, 0, overSized.Length);
}
catch (InvalidCipherTextException e)
{
@@ -104,22 +118,22 @@ namespace Org.BouncyCastle.Crypto.Tests
private void doTestTruncatedPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block truncated");
+ checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block incorrect");
}
private void doTestDudPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "unknown block type");
+ checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "block incorrect");
}
private void doTestWrongPaddingPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block padding incorrect");
+ checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block incorrect");
}
private void doTestMissingDataPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
{
- checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "no data in block");
+ checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "block incorrect");
}
private void checkForPkcs1Exception(RsaKeyParameters pubParameters, RsaKeyParameters privParameters, byte[] inputData, string expectedMessage)
@@ -431,30 +445,65 @@ namespace Org.BouncyCastle.Crypto.Tests
eng.Init(false, privParameters);
- try
- {
- data = eng.ProcessBlock(data, 0, data.Length);
+ byte[] plainData = null;
+ try
+ {
+ plainData = eng.ProcessBlock(data, 0, data.Length);
}
catch (Exception e)
{
Fail("failed - exception " + e.ToString());
}
- if (!input.Equals(Hex.ToHexString(data)))
- {
- Fail("failed PKCS1 public/private Test");
+ if (!input.Equals(Hex.ToHexString(plainData)))
+ {
+ Fail("failed PKCS1 public/private Test");
}
- //
- // PKCS1 - private encrypt, public decrypt
- //
- eng = new Pkcs1Encoding(((Pkcs1Encoding)eng).GetUnderlyingCipher());
+ Pkcs1Encoding fEng = new Pkcs1Encoding(new RsaEngine(), input.Length / 2);
+ fEng.Init(false, new ParametersWithRandom(privParameters, new SecureRandom()));
+ try
+ {
+ plainData = fEng.ProcessBlock(data, 0, data.Length);
+ }
+ catch (Exception e)
+ {
+ Fail("failed - exception " + e.ToString(), e);
+ }
+
+ if (!input.Equals(Hex.ToHexString(plainData)))
+ {
+ Fail("failed PKCS1 public/private fixed Test");
+ }
+
+ fEng = new Pkcs1Encoding(new RsaEngine(), input.Length);
+ fEng.Init(false, new ParametersWithRandom(privParameters, new SecureRandom()));
+ try
+ {
+ data = fEng.ProcessBlock(data, 0, data.Length);
+ }
+ catch (Exception e)
+ {
+ Fail("failed - exception " + e.ToString(), e);
+ }
+
+ if (input.Equals(Hex.ToHexString(data)))
+ {
+ Fail("failed to recognise incorrect plaint text length");
+ }
+
+ data = plainData;
+
+ //
+ // PKCS1 - private encrypt, public decrypt
+ //
+ eng = new Pkcs1Encoding(((Pkcs1Encoding)eng).GetUnderlyingCipher());
eng.Init(true, privParameters);
try
{
- data = eng.ProcessBlock(data, 0, data.Length);
+ data = eng.ProcessBlock(plainData, 0, plainData.Length);
}
catch (Exception e)
{
|