static string encrypt(string plaintext, string password, int nBits) { const int blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES if (!(nBits == 128 || nBits == 192 || nBits == 256)) { Console.WriteLine("Key size is not 128 / 192 / 256"); } // use AES itself to encrypt password to get cipher key (using plain password as source for key // expansion) to give us well encrypted key (in real use hashed password could be used for key) int nBytes = nBits / 8; // no bytes in key (16/24/32) byte[] pwBytes = new byte[nBytes]; for (int i = 0; i < nBytes; i++) { // use 1st 16/24/32 chars of password for key pwBytes[i] = i < password.Length ? Convert.ToByte(password[i]) : (byte)0; } Aes aes = new Aes(); byte[] key = aes.cipher(pwBytes, aes.keyExpansion(pwBytes)); // gives us 16-byte key key = key.Concat(key.Slice(0, nBytes - 16)).ToArray(); // expand key to 16/24/32 bytes long // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec, // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106 byte[] counterBlock = new byte[(blockSize)]; uint nonce = 0, nonceMs = 0, nonceSec = 0, nonceRnd = 0; for (int i = 0; i < 2; i++) { counterBlock[i] = Convert.ToByte((nonceMs >> i * 8) & 0xff); } for (int i = 0; i < 2; i++) { counterBlock[i + 2] = Convert.ToByte((nonceRnd >> i * 8) & 0xff); } for (int i = 0; i < 4; i++) { counterBlock[i + 4] = Convert.ToByte((nonceSec >> i * 8) & 0xff); } // and convert it to a string to go on the front of the ciphertext string ctrTxt = ""; for (int i = 0; i < 8; i++) { ctrTxt += (char)counterBlock[i]; } // generate key schedule - an expansion of the key into distinct Key Rounds for each round byte[,] keySchedule = aes.keyExpansion(key); double blockCount = Math.Ceiling(((double)plaintext.Length / blockSize)); string ciphertext = ""; for (int b = 0; b < blockCount; b++) { // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) for (int c = 0; c < 4; c++) { counterBlock[15 - c] = Convert.ToByte((uint)(b >> c * 8) & 0xff); } for (int c = 0; c < 4; c++) { counterBlock[15 - c - 4] = Convert.ToByte((uint)(b / 0x100000000 >> c * 8)); } byte[] cipherCntr = aes.cipher(counterBlock, keySchedule); // -- encrypt counter block -- // block size is reduced on final block int blockLength = b < blockCount - 1 ? blockSize : (plaintext.Length - 1) % blockSize + 1; char[] cipherChar = new char[(blockLength)]; for (int i = 0; i < blockLength; i++) { // -- xor plaintext with ciphered counter char-by-char -- cipherChar[i] = Convert.ToChar(cipherCntr[i] ^ plaintext[(b * blockSize + i)]); cipherChar[i] = (cipherChar[i]); } ciphertext = ciphertext + string.Join("", cipherChar); } ciphertext = Base64Encode(ctrTxt + ciphertext); return(ciphertext); }
/** * Decrypt a text encrypted by AES in counter mode of operation * * @param {string} ciphertext - Cipher text to be decrypted. * @param {string} password - Password to use to generate a key for decryption. * @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256. * @returns {string} Decrypted text * * @example * const decr = AesCtr.decrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // 'big secret' */ static string decrypt(string ciphertext, string password, int nBits) { const int blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES if (!(nBits == 128 || nBits == 192 || nBits == 256)) { Console.WriteLine("Key size is not 128 / 192 / 256"); } ciphertext = Base64Decode(ciphertext); // use AES to encrypt password (mirroring encrypt routine) int nBytes = nBits / 8; // no bytes in key byte[] pwBytes = new byte[(nBytes)]; for (int i = 0; i < nBytes; i++) { // use 1st nBytes chars of password for key pwBytes[i] = i < password.Length ? Convert.ToByte(password[(i)]) : Convert.ToByte(0); } Aes aes = new Aes(); byte[] key = aes.cipher(pwBytes, aes.keyExpansion(pwBytes)); key = key.Concat(key.Slice(0, nBytes - 16)).ToArray(); // expand key to 16/24/32 bytes long // recover nonce from 1st 8 bytes of ciphertext byte[] counterBlock = new byte[10000]; string ctrTxt = ciphertext.Slice(0, 8); for (int i = 0; i < 8; i++) { counterBlock[i] = Convert.ToByte(ctrTxt[(i)]); } // generate key schedule byte[,] keySchedule = aes.keyExpansion(key); // separate ciphertext into blocks (skipping past initial 8 bytes) double nBlocks = Math.Ceiling((double)(ciphertext.Length - 8) / blockSize); string[] ct = new string[((int)nBlocks)]; for (int b = 0; b < nBlocks; b++) { ct[b] = ciphertext.Slice(8 + b * blockSize, 8 + b * blockSize + blockSize); } string[] ciphertext1 = ct; // ciphertext is now array of block-length strings // plaintext will get generated block-by-block into array of block-length strings string plaintext = ""; for (int b = 0; b < nBlocks; b++) { // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) for (int c = 0; c < 4; c++) { byte byte1 = Convert.ToByte(((uint)(b) >> c * 8) & 0xff); counterBlock[15 - c] = Convert.ToByte(((uint)(b) >> c * 8) & 0xff); } for (int c = 0; c < 4; c++) { double byte1 = ((double)(b + 1) / 0x100000000 - 1); byte byte2 = Convert.ToByte(((uint)byte1 >> c * 8) & 0xff); counterBlock[15 - c - 4] = byte2; } byte[] cipherCntr = aes.cipher(counterBlock, keySchedule); // encrypt counter block char[] plaintxtByte = new char[(ciphertext1[b].Length)]; for (int i = 0; i < ciphertext1[b].Length; i++) { // -- xor plaintext with ciphered counter byte-by-byte -- plaintxtByte[i] = Convert.ToChar(cipherCntr[i] ^ Convert.ToByte(ciphertext1[b][(i)])); } plaintext = plaintext + string.Join("", plaintxtByte); } return(plaintext); }