1 // 2 // CFB Unit Tests 3 // 4 // Author: 5 // Sebastien Pouliot <sebastien@xamarin.com> 6 // 7 // Copyright (C) 2013 Xamarin Inc (http://www.xamarin.com) 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining 10 // a copy of this software and associated documentation files (the 11 // "Software"), to deal in the Software without restriction, including 12 // without limitation the rights to use, copy, modify, merge, publish, 13 // distribute, sublicense, and/or sell copies of the Software, and to 14 // permit persons to whom the Software is furnished to do so, subject to 15 // the following conditions: 16 // 17 // The above copyright notice and this permission notice shall be 18 // included in all copies or substantial portions of the Software. 19 // 20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 // 28 29 using System; 30 using System.Collections.Generic; 31 using System.Security.Cryptography; 32 33 using NUnit.Framework; 34 35 namespace MonoTests.System.Security.Cryptography { 36 37 public abstract class CfbTests { 38 GetInstance()39 protected abstract SymmetricAlgorithm GetInstance (); 40 ProcessBlockSizes(SymmetricAlgorithm algo)41 protected void ProcessBlockSizes (SymmetricAlgorithm algo) 42 { 43 algo.Padding = PaddingMode.None; 44 foreach (KeySizes bs in algo.LegalBlockSizes) { 45 for (int blockSize = bs.MinSize; blockSize <= bs.MaxSize; blockSize += bs.SkipSize) { 46 algo.BlockSize = blockSize; 47 ProcessKeySizes (algo); 48 // SkipSize can be 0 (e.g. DES) if only one block size is available 49 if (blockSize == bs.MaxSize) 50 break; 51 } 52 } 53 } 54 ProcessKeySizes(SymmetricAlgorithm algo)55 protected void ProcessKeySizes (SymmetricAlgorithm algo) 56 { 57 foreach (KeySizes ks in algo.LegalKeySizes) { 58 for (int keySize = ks.MinSize; keySize <= ks.MaxSize; keySize += ks.SkipSize) { 59 algo.KeySize = keySize; 60 algo.Key = GetKey (algo); 61 algo.IV = new byte [algo.BlockSize / 8]; 62 ProcessPadding (algo); 63 // SkipSize can be 0 (e.g. DES) if only one key size is available 64 if (keySize == ks.MaxSize) 65 break; 66 } 67 } 68 } 69 70 protected abstract PaddingMode[] PaddingModes { get; } 71 ProcessPadding(SymmetricAlgorithm algo)72 protected void ProcessPadding (SymmetricAlgorithm algo) 73 { 74 foreach (var padding in PaddingModes) { 75 algo.Padding = padding; 76 CFB (algo); 77 } 78 } 79 GetKey(SymmetricAlgorithm algo)80 protected virtual byte [] GetKey (SymmetricAlgorithm algo) 81 { 82 return new byte [algo.KeySize / 8]; 83 } 84 CFB(SymmetricAlgorithm algo)85 protected abstract void CFB (SymmetricAlgorithm algo); 86 GetId(SymmetricAlgorithm algo)87 protected int GetId (SymmetricAlgorithm algo) 88 { 89 return (algo.BlockSize << 24) + (algo.KeySize << 16) + ((int) algo.Padding << 8) + algo.FeedbackSize; 90 } 91 GetExpectedResult(SymmetricAlgorithm algo, byte [] encryptedData)92 protected virtual string GetExpectedResult (SymmetricAlgorithm algo, byte [] encryptedData) 93 { 94 int id = GetId (algo); 95 string expected = BitConverter.ToString (encryptedData); 96 Console.WriteLine ("// block size: {0}, key size: {1}, padding: {2}, feedback: {3}", algo.BlockSize, algo.KeySize, algo.Padding, algo.FeedbackSize); 97 Console.WriteLine ("{{ {0}, \"{1}\" }},", id, expected); 98 return expected; 99 } 100 CFB(SymmetricAlgorithm algo, int feedbackSize)101 protected void CFB (SymmetricAlgorithm algo, int feedbackSize) 102 { 103 byte [] data = new byte [feedbackSize >> 3]; 104 for (int i = 0; i < data.Length; i++) 105 data [i] = (byte) (0xff - i); 106 byte [] encdata = Encryptor (algo, data); 107 string expected = GetExpectedResult (algo, encdata); 108 string actual = null; 109 if (algo.Padding == PaddingMode.ISO10126) { 110 // ISO10126 uses random data so we can't compare the last bytes with a test vector 111 actual = BitConverter.ToString (encdata, 0, data.Length); 112 expected = expected.Substring (0, actual.Length); 113 } else { 114 actual = BitConverter.ToString (encdata); 115 } 116 Assert.AreEqual (expected, actual, "encrypted value"); 117 byte [] decdata = Decryptor (algo, encdata); 118 if (algo.Padding == PaddingMode.Zeros) { 119 // this requires manually unpadding the decrypted data - but unlike ISO10126 120 // we know the rest of the data will be 0 (not random) so we check that 121 byte [] resize = new byte [data.Length]; 122 Array.Copy (decdata, 0, resize, 0, resize.Length); 123 // all zeros afterward! 124 for (int i = resize.Length; i < decdata.Length; i++) 125 Assert.AreEqual (0, decdata [i], "padding zero {0}", i); 126 decdata = resize; 127 } 128 Assert.AreEqual (data, decdata, "Roundtrip {0} {1}", algo.Mode, algo.FeedbackSize); 129 } 130 GetTransformBlockSize(SymmetricAlgorithm algo)131 protected virtual int GetTransformBlockSize (SymmetricAlgorithm algo) 132 { 133 return algo.BlockSize / 8; 134 } 135 Encryptor(SymmetricAlgorithm algo, byte [] data)136 byte [] Encryptor (SymmetricAlgorithm algo, byte [] data) 137 { 138 using (ICryptoTransform t = algo.CreateEncryptor (algo.Key, algo.IV)) { 139 int size = GetTransformBlockSize (algo); 140 Assert.That (t.InputBlockSize == size, "Encryptor InputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize); 141 Assert.That (t.OutputBlockSize == size, "Encryptor OutputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize); 142 return t.TransformFinalBlock (data, 0, data.Length); 143 } 144 } 145 Decryptor(SymmetricAlgorithm algo, byte [] encdata)146 byte [] Decryptor (SymmetricAlgorithm algo, byte [] encdata) 147 { 148 using (ICryptoTransform t = algo.CreateDecryptor (algo.Key, algo.IV)) { 149 int size = GetTransformBlockSize (algo); 150 Assert.That (t.InputBlockSize == size, "Decryptor InputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize); 151 Assert.That (t.OutputBlockSize == size, "Decryptor OutputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize); 152 return t.TransformFinalBlock (encdata, 0, encdata.Length); 153 } 154 } 155 } 156 157 // most algorithms are "limited" and only support CFB8 158 public abstract class LimitedCfbTests : CfbTests { 159 160 // all *CryptoServiceProvider implementation refuse Padding.None 161 static PaddingMode[] csp_padding_modes = new [] { PaddingMode.PKCS7, PaddingMode.Zeros, PaddingMode.ANSIX923, PaddingMode.ISO10126 }; 162 163 protected override PaddingMode [] PaddingModes { 164 get { return csp_padding_modes; } 165 } 166 167 [Test] 168 [ExpectedException (typeof (CryptographicException))] Cfb_None()169 public void Cfb_None () 170 { 171 using (var algo = GetInstance ()) { 172 algo.Padding = PaddingMode.None; 173 CFB (algo, 8); 174 } 175 } 176 CFB(SymmetricAlgorithm algo)177 protected override void CFB (SymmetricAlgorithm algo) 178 { 179 algo.Mode = CipherMode.CFB; 180 // System.Security.Cryptography.CryptographicException : Feedback size for the cipher feedback mode (CFB) must be 8 bits. 181 algo.FeedbackSize = 8; 182 CFB (algo, algo.FeedbackSize); 183 } 184 } 185 186 // DES and 3DES won't accept a key with all zero (since it's a weak key for them) 187 public abstract class WeakKeyCfbTests : LimitedCfbTests { 188 GetKey(SymmetricAlgorithm algo)189 protected override byte [] GetKey (SymmetricAlgorithm algo) 190 { 191 var key = base.GetKey (algo); 192 for (byte i = 0; i < key.Length; i++) 193 key [i] = i; 194 return key; 195 } 196 } 197 }