Browse Source

Fix CFB mode for Rijndael when data is not a multiple of block size

Sebastien Pouliot 13 years ago
parent
commit
e094d3dc0c

+ 32 - 11
mcs/class/corlib/Mono.Security.Cryptography/SymmetricTransform.cs

@@ -3,10 +3,11 @@
 //
 // Authors:
 //	Thomas Neidhart ([email protected])
-//	Sebastien Pouliot <sebastien@ximian.com>
+//  Sebastien Pouliot  <sebastien@xamarin.com>
 //
 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
 // Copyright (C) 2004-2008 Novell, Inc (http://www.novell.com)
+// Copyright 2012 Xamarin Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -376,19 +377,20 @@ namespace Mono.Security.Cryptography {
 				// we need to add an extra block for padding
 				total += BlockSizeByte;
 				break;
+			case PaddingMode.None:
+				if ((rem != 0) && (algo.Mode != CipherMode.CFB))
+					throw new CryptographicException ("invalid block length");
+				goto default;
 			default:
 				if (inputCount == 0)
 					return new byte [0];
 				if (rem != 0) {
-					if (algo.Padding == PaddingMode.None)
-						throw new CryptographicException ("invalid block length");
 					// zero padding the input (by adding a block for the partial data)
 					byte[] paddedInput = new byte [full + BlockSizeByte];
 					Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
 					inputBuffer = paddedInput;
 					inputOffset = 0;
-					inputCount = paddedInput.Length;
-					total = inputCount;
+					total = paddedInput.Length;
 				}
 				break;
 			}
@@ -438,8 +440,16 @@ namespace Mono.Security.Cryptography {
 				// the last padded block will be transformed in-place
 				InternalTransformBlock (res, full, BlockSizeByte, res, full);
 				break;
-			default:
+			case PaddingMode.Zeros:
+				InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
+				break;
+			case PaddingMode.None:
 				InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
+				if ((inputCount != total) && (algo.Mode == CipherMode.CFB)) {
+					byte[] part = new byte [inputCount];
+					Buffer.BlockCopy (res, 0, part, 0, inputCount);
+					res = part;
+				}
 				break;
 			}
 #endif // NET_2_1
@@ -448,21 +458,29 @@ namespace Mono.Security.Cryptography {
 
 		private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
 		{
-			if ((inputCount % BlockSizeByte) > 0)
-				throw new CryptographicException ("Invalid input block size.");
+			int full = (inputCount / BlockSizeByte) * BlockSizeByte;
+			int rem = inputCount - full;
+			if (rem > 0) {
+				if (algo.Mode != CipherMode.CFB)
+					throw new CryptographicException ("Invalid input block size.");
+				full += BlockSizeByte;
+				byte[] paddedInput = new byte [full];
+				Buffer.BlockCopy (inputBuffer, 0, paddedInput, 0, inputCount);
+				inputBuffer = paddedInput;
+			}
 
-			int total = inputCount;
+			int total = full;
 			if (lastBlock)
 				total += BlockSizeByte;
 
 			byte[] res = new byte [total];
 			int outputOffset = 0;
 
-			while (inputCount > 0) {
+			while (full > 0) {
 				int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
 				inputOffset += BlockSizeByte;
 				outputOffset += len;
-				inputCount -= BlockSizeByte;
+				full -= BlockSizeByte;
 			}
 
 			if (lastBlock) {
@@ -509,6 +527,9 @@ namespace Mono.Security.Cryptography {
 				total -= padding;
 				break;
 			case PaddingMode.None:	// nothing to do - it's a multiple of block size
+				if (algo.Mode == CipherMode.CFB)
+					total = inputCount;
+				break;
 			case PaddingMode.Zeros:	// nothing to do - user must unpad himself
 				break;
 			}

+ 25 - 1
mcs/class/corlib/Test/System.Security.Cryptography/RijndaelManagedTest.cs

@@ -3,9 +3,10 @@
 //
 // Authors:
 //      Andrew Birkett ([email protected])
-//      Sebastien Pouliot  <sebastien@ximian.com>
+//      Sebastien Pouliot  <sebastien@xamarin.com>
 //
 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
+// Copyright 2012 Xamarin Inc.
 //
 
 using System;
@@ -363,5 +364,28 @@ namespace MonoTests.System.Security.Cryptography {
 			CreateDecryptor_IV (size);
 		}
 #endif
+		[Test]
+		public void CFB_7193 ()
+		{
+			const int size = 23; // not a block size
+			byte [] original = new byte [size];
+			byte [] expected = new byte [] { 0xDC, 0xA8, 0x39, 0x5C, 0xA1, 0x89, 0x3B, 0x05, 0xFA, 0xD8, 0xB5, 0x76, 0x5F, 0x8F, 0x40, 0xCF, 0xA7, 0xFF, 0x86, 0xE6, 0x30, 0x67, 0x6B };
+			byte [] encdata;
+			byte [] decdata;
+			using (RijndaelManaged aes = new RijndaelManaged ()) {
+				aes.Mode = CipherMode.CFB;
+				aes.FeedbackSize = 8;
+				aes.Padding = PaddingMode.None;
+				aes.Key = new byte [32];
+				aes.IV = new byte [16];
+				using (ICryptoTransform encryptor = aes.CreateEncryptor ())
+					encdata = encryptor.TransformFinalBlock (original, 0, original.Length);
+				Assert.AreEqual (encdata.Length, size, "enc.Length");
+				Assert.AreEqual (encdata, expected, "encrypted");
+				using (ICryptoTransform decryptor = aes.CreateDecryptor ())
+					decdata = decryptor.TransformFinalBlock (encdata, 0, encdata.Length);
+				Assert.AreEqual (decdata, original, "roundtrip");
+			}
+		}
 	}
 }