}//NextBytes() void NextBytesInternal(ArraySegment<byte> bufferSegment) { BCrypt.NTSTATUS status; var buffer = bufferSegment.Array; var offset = bufferSegment.Offset; var count = bufferSegment.Count; if (count > CACHE_THRESHOLD) { status = (offset == 0) ? BCrypt.BCryptGenRandom(buffer, count) : BCrypt.BCryptGenRandom_PinnedBuffer(buffer, offset, count); if (status == BCrypt.NTSTATUS.STATUS_SUCCESS) return; throw new CryptographicException((int)status); } lock (_byteCache) { if (_byteCachePosition + count <= BYTE_CACHE_SIZE) { Utils.BlockCopy(_byteCache, _byteCachePosition, buffer, offset, count); _byteCachePosition += count; return; } status = BCrypt.BCryptGenRandom(_byteCache, BYTE_CACHE_SIZE); if (status == BCrypt.NTSTATUS.STATUS_SUCCESS) { _byteCachePosition = count; Utils.BlockCopy(_byteCache, 0, buffer, offset, count); return; } throw new CryptographicException((int)status); }// lock }//NextBytesInternal()
}// Decrypt() public static byte[] Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, ArraySegment<byte>? salt = null, uint counter = 1) { int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH; if (cipherLength < Cipher.AesConstants.AES_BLOCK_SIZE) return null; try { Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(ciphertext.Array, ciphertext.Offset, CONTEXT_BUFFER_LENGTH), derivedOutput: _sessionKey.Value.AsArraySegment(), counter: counter); Utils.BlockCopy(_sessionKey.Value, 0, _macKey.Value, 0, MAC_KEY_LENGTH); using (var hmac = _hmacFactory()) { hmac.Key = _macKey.Value; hmac.TransformBlock(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, AES_IV_LENGTH + cipherLength, null, 0); hmac.TransformFinalBlock(ciphertext.Array, 0, 0); var fullmacActual = hmac.HashInner; if (!Utils.ConstantTimeEqual(fullmacActual, 0, ciphertext.Array, ciphertext.Offset + ciphertext.Count - MAC_LENGTH, MAC_LENGTH)) return null; }// using hmac Utils.BlockCopy(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, _iv.Value, 0, AES_IV_LENGTH); Utils.BlockCopy(_sessionKey.Value, MAC_KEY_LENGTH, _encKey.Value, 0, ENC_KEY_LENGTH); using (var aes = _aesFactory()) { EtM_CBC.ValidateAes(aes); using (var aesDecryptor = aes.CreateDecryptor(_encKey.Value, _iv.Value)) { return aesDecryptor.TransformFinalBlock(ciphertext.Array, ciphertext.Offset + CONTEXT_BUFFER_LENGTH, cipherLength); }// using aesDecryptor }// using aes } finally { EtM_CBC.ClearKeyMaterial(); } }// Decrypt()
}//NextBytes() void NextBytesInternal(byte[] buffer, int offset, int count) { BCrypt.NTSTATUS status; if (count > CACHE_THRESHOLD) { status = (offset == 0) ? BCrypt.BCryptGenRandom(buffer, count) : BCrypt.BCryptGenRandom_WithOffset(buffer, offset, count); if (status == BCrypt.NTSTATUS.STATUS_SUCCESS) return; ThrowNewCryptographicException((int)status); } lock (_byteCache) { if (_byteCachePosition + count <= BYTE_CACHE_SIZE) { Utils.BlockCopy(_byteCache, _byteCachePosition, buffer, offset, count); _byteCachePosition += count; return; } status = BCrypt.BCryptGenRandom(_byteCache, BYTE_CACHE_SIZE); if (status == BCrypt.NTSTATUS.STATUS_SUCCESS) { _byteCachePosition = count; Utils.BlockCopy(_byteCache, 0, buffer, offset, count); return; } ThrowNewCryptographicException((int)status); }// lock }//NextBytesInternal()
}// Encrypt() public static void Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, ref ArraySegment<byte>? outputSegment, ArraySegment<byte>? salt = null, uint counter = 1) { int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH; if (cipherLength < 0) { outputSegment = null; return; } try { Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(ciphertext.Array, ciphertext.Offset, CONTEXT_TWEAK_LENGTH), derivedOutput: new ArraySegment<byte>(_sessionKey.Value), counter: counter); Utils.BlockCopy(_sessionKey.Value, 0, _macKey.Value, 0, MAC_KEY_LENGTH); using (var hmac = _hmacFactory()) { hmac.Key = _macKey.Value; hmac.TransformBlock(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, NONCE_LENGTH + cipherLength, null, 0); hmac.TransformFinalBlock(ciphertext.Array, 0, 0); var fullmacActual = hmac.HashInner; if (!Utils.ConstantTimeEqual(fullmacActual, 0, ciphertext.Array, ciphertext.Offset + ciphertext.Count - MAC_LENGTH, MAC_LENGTH)) { outputSegment = null; return; }; }// using hmac if (outputSegment == null) outputSegment = (new byte[cipherLength]).AsNullableArraySegment(); Utils.BlockCopy(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, _counterBuffer.Value, 0, NONCE_LENGTH); Utils.BlockCopy(_sessionKey.Value, MAC_KEY_LENGTH, _encKey.Value, 0, ENC_KEY_LENGTH); using (var ctrTransform = new Cipher.AesCtrCryptoTransform(key: _encKey.Value, counterBufferSegment: _counterBuffer.Value.AsArraySegment(), aesFactory: _aesFactory)) { ctrTransform.TransformBlock(inputBuffer: ciphertext.Array, inputOffset: ciphertext.Offset + CONTEXT_BUFFER_LENGTH, inputCount: cipherLength, outputBuffer: outputSegment.GetValueOrDefault().Array, outputOffset: outputSegment.GetValueOrDefault().Offset); }// using aesDecryptor } finally { EtM_CTR.ClearKeyMaterial(); } }// Decrypt()
public static void Encrypt(byte[] masterKey, ArraySegment<byte> plaintext, byte[] output, int outputOffset, ArraySegment<byte>? salt = null, uint counter = 1) { int ciphertextLength = CONTEXT_BUFFER_LENGTH + plaintext.Count + MAC_LENGTH; if (output.Length - outputOffset < ciphertextLength) throw new ArgumentOutOfRangeException(nameof(output), $"'{nameof(output)}' array segment is not big enough for the ciphertext"); try { _cryptoRandom.NextBytes(_contextBuffer.Value); Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(_contextBuffer.Value, 0, CONTEXT_TWEAK_LENGTH), derivedOutput: _sessionKey.Value.AsArraySegment(), counter: counter); Utils.BlockCopy(_sessionKey.Value, 0, _macKey.Value, 0, MAC_KEY_LENGTH); Utils.BlockCopy(_sessionKey.Value, MAC_KEY_LENGTH, _encKey.Value, 0, ENC_KEY_LENGTH); Utils.BlockCopy(_contextBuffer.Value, 0, output, outputOffset, CONTEXT_BUFFER_LENGTH); Utils.BlockCopy(_contextBuffer.Value, CONTEXT_TWEAK_LENGTH, _counterBuffer.Value, 0, NONCE_LENGTH); using (var ctrTransform = new Cipher.AesCtrCryptoTransform(key: _encKey.Value, counterBufferSegment: _counterBuffer.Value.AsArraySegment(), aesFactory: _aesFactory)) { ctrTransform.TransformBlock(inputBuffer: plaintext.Array, inputOffset: plaintext.Offset, inputCount: plaintext.Count, outputBuffer: output, outputOffset: outputOffset + CONTEXT_BUFFER_LENGTH); }// using aesEncryptor using (var hmac = _hmacFactory()) { hmac.Key = _macKey.Value; hmac.TransformBlock(output, outputOffset + CONTEXT_TWEAK_LENGTH, NONCE_LENGTH + plaintext.Count, null, 0); hmac.TransformFinalBlock(output, 0, 0); var fullmac = hmac.HashInner; Utils.BlockCopy(fullmac, 0, output, outputOffset + ciphertextLength - MAC_LENGTH, MAC_LENGTH); }// using hmac } finally { EtM_CTR.ClearKeyMaterial(); } }// Encrypt()
}// Encrypt() public static void Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, ref ArraySegment<byte>? outputSegment, ArraySegment<byte>? salt = null, uint counter = 1) { int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH; if (cipherLength < Cipher.AesConstants.AES_BLOCK_SIZE) { outputSegment = null; return; } int fullBlockLength = cipherLength - AES_IV_LENGTH; byte[] finalBlock = null; try { var iv = _iv.Value; var encKey = _encKey.Value; var macKey = _macKey.Value; var sessionKey = _sessionKey.Value; Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(ciphertext.Array, ciphertext.Offset, CONTEXT_BUFFER_LENGTH), derivedOutput: sessionKey.AsArraySegment(), counter: counter); Utils.BlockCopy(sessionKey, 0, macKey, 0, MAC_KEY_LENGTH); using (var hmac = _hmacFactory()) { hmac.Key = macKey; hmac.TransformBlock(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, AES_IV_LENGTH + cipherLength, null, 0); hmac.TransformFinalBlock(ciphertext.Array, 0, 0); var fullmacActual = hmac.HashInner; if (!Utils.ConstantTimeEqual(fullmacActual, 0, ciphertext.Array, ciphertext.Offset + ciphertext.Count - MAC_LENGTH, MAC_LENGTH)) { outputSegment = null; return; }; }// using hmac Utils.BlockCopy(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, iv, 0, AES_IV_LENGTH); Utils.BlockCopy(sessionKey, MAC_KEY_LENGTH, encKey, 0, ENC_KEY_LENGTH); using (var aes = _aesFactory()) { EtM_CBC.ValidateAes(aes); using (var aesDecryptor = aes.CreateDecryptor(encKey, iv)) { int fullBlockTransformed = 0; if (fullBlockLength > 0) fullBlockTransformed = aesDecryptor.TransformBlock(inputBuffer: ciphertext.Array, inputOffset: ciphertext.Offset + CONTEXT_BUFFER_LENGTH, inputCount: fullBlockLength, outputBuffer: outputSegment.Value.Array, outputOffset: outputSegment.Value.Offset); finalBlock = aesDecryptor.TransformFinalBlock(ciphertext.Array, ciphertext.Offset + CONTEXT_BUFFER_LENGTH + fullBlockLength, cipherLength - fullBlockLength); Utils.BlockCopy(finalBlock, 0, outputSegment.Value.Array, outputSegment.Value.Offset + fullBlockTransformed, finalBlock.Length); outputSegment = new ArraySegment<byte>?(new ArraySegment<byte>(outputSegment.Value.Array, outputSegment.Value.Offset, fullBlockTransformed + finalBlock.Length)); }// using aesDecryptor }// using aes } finally { EtM_CBC.ClearKeyMaterial(); if (finalBlock != null) Array.Clear(finalBlock, 0, finalBlock.Length); } }// Decrypt()
public static void Encrypt(byte[] masterKey, ArraySegment<byte> plaintext, byte[] output, int outputOffset, ArraySegment<byte>? salt = null, uint counter = 1) { int fullBlockLength = plaintext.Count & (-Cipher.AesConstants.AES_BLOCK_SIZE); int finalBlockLength = plaintext.Count % Cipher.AesConstants.AES_BLOCK_SIZE; int paddingLength = Cipher.AesConstants.AES_BLOCK_SIZE - finalBlockLength; int ciphertextLength = CONTEXT_BUFFER_LENGTH + plaintext.Count + paddingLength + MAC_LENGTH; if (output.Length - outputOffset < ciphertextLength) throw new ArgumentOutOfRangeException(nameof(output), $"'{nameof(output)}' array segment is not big enough for the ciphertext"); try { var iv = _iv.Value; var contextBuffer = _contextBuffer.Value; var encKey = _encKey.Value; var macKey = _macKey.Value; var sessionKey = _sessionKey.Value; using (var aes = _aesFactory()) { EtM_CBC.ValidateAes(aes); _cryptoRandom.NextBytes(contextBuffer, 0, CONTEXT_BUFFER_LENGTH); Utils.BlockCopy(contextBuffer, CONTEXT_TWEAK_LENGTH, iv, 0, AES_IV_LENGTH); Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: contextBuffer.AsArraySegment(), derivedOutput: sessionKey.AsArraySegment(), counter: counter); Utils.BlockCopy(sessionKey, 0, macKey, 0, MAC_KEY_LENGTH); Utils.BlockCopy(sessionKey, MAC_KEY_LENGTH, encKey, 0, ENC_KEY_LENGTH); Utils.BlockCopy(contextBuffer, 0, output, outputOffset, CONTEXT_BUFFER_LENGTH); using (var aesEncryptor = aes.CreateEncryptor(encKey, iv)) { if (fullBlockLength > 0) aesEncryptor.TransformBlock(inputBuffer: plaintext.Array, inputOffset: plaintext.Offset, inputCount: fullBlockLength, outputBuffer: output, outputOffset: outputOffset + CONTEXT_BUFFER_LENGTH); var finalBlockBuffer = aesEncryptor.TransformFinalBlock(inputBuffer: plaintext.Array, inputOffset: plaintext.Offset + fullBlockLength, inputCount: finalBlockLength); Utils.BlockCopy(finalBlockBuffer, 0, output, outputOffset + CONTEXT_BUFFER_LENGTH + fullBlockLength, finalBlockBuffer.Length); }// using aesEncryptor }// using aes using (var hmac = _hmacFactory()) { hmac.Key = macKey; hmac.TransformBlock(output, outputOffset + CONTEXT_TWEAK_LENGTH, AES_IV_LENGTH + plaintext.Count + paddingLength, null, 0); hmac.TransformFinalBlock(output, 0, 0); var fullmac = hmac.HashInner; Utils.BlockCopy(fullmac, 0, output, outputOffset + ciphertextLength - MAC_LENGTH, MAC_LENGTH); }// using hmac } finally { EtM_CBC.ClearKeyMaterial(); } }// Encrypt()
internal static void PrependSaltWith1stBlockContext(ref ArraySegment<byte>? salt, byte[] contextBuffer, int contextBufferOffset) { int saltLength = salt?.Count ?? 0; var streamContextCombinedWithSalt = new byte[EtM_CTR.CONTEXT_TWEAK_LENGTH + saltLength]; for (int i = EtM_CTR.CONTEXT_TWEAK_LENGTH - 1; i >= 0; --i) { streamContextCombinedWithSalt[i] = contextBuffer[contextBufferOffset + i]; } if (saltLength > 0) { var saltValue = salt.GetValueOrDefault(); Utils.BlockCopy(saltValue.Array, saltValue.Offset, streamContextCombinedWithSalt, EtM_CTR.CONTEXT_TWEAK_LENGTH, saltLength); } salt = new ArraySegment<byte>(streamContextCombinedWithSalt); }// PrependSaltWith1stBlockContext()
void NextBytesInternal(ArraySegment<byte> bufferSegment) { BCrypt.NTSTATUS status; var buffer = bufferSegment.Array; var offset = bufferSegment.Offset; var count = bufferSegment.Count; if (count > CACHE_THRESHOLD) { status = (offset == 0) ? BCrypt.BCryptGenRandom(buffer, count) : BCrypt.BCryptGenRandom_PinnedBuffer(buffer, offset, count); if (status == BCrypt.NTSTATUS.STATUS_SUCCESS) return; throw new CryptographicException((int)status); } while (true) { int currentByteCachePosition = Interlocked.Add(ref _byteCachePosition, count); if (currentByteCachePosition <= BYTE_CACHE_SIZE && currentByteCachePosition > 0) { Utils.BlockCopy(_byteCache, currentByteCachePosition - count, buffer, 0, count); return; } lock (_byteCache) { currentByteCachePosition = _byteCachePosition; // atomic read if (currentByteCachePosition > (BYTE_CACHE_SIZE - count) || currentByteCachePosition <= 0) { status = BCrypt.BCryptGenRandom(_byteCache, BYTE_CACHE_SIZE); if (status == BCrypt.NTSTATUS.STATUS_SUCCESS) { _byteCachePosition = count; // atomic write Utils.BlockCopy(_byteCache, 0, buffer, 0, count); return; } // defensive logic to prevent _byteCachePosition from wrapping into valid range due to BCryptGenRandom failures if (currentByteCachePosition > BYTE_CACHE_SIZE || currentByteCachePosition < 0) _byteCachePosition = BYTE_CACHE_SIZE; throw new CryptographicException((int)status); }// if outside the valid range }// lock }// while(true) }//NextBytes()
}// Decrypt() public static bool Authenticate(byte[] masterKey, ArraySegment<byte> ciphertext, ArraySegment<byte>? salt = null, uint counter = 1) { int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH; if (cipherLength < 0) return false; try { Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(ciphertext.Array, ciphertext.Offset, CONTEXT_TWEAK_LENGTH), derivedOutput: _sessionKey.Value.AsArraySegment(), counter: counter); Utils.BlockCopy(_sessionKey.Value, 0, _macKey.Value, 0, MAC_KEY_LENGTH); using (var hmac = _hmacFactory()) { hmac.Key = _macKey.Value; hmac.TransformBlock(ciphertext.Array, ciphertext.Offset + CONTEXT_TWEAK_LENGTH, NONCE_LENGTH + cipherLength, null, 0); hmac.TransformFinalBlock(ciphertext.Array, 0, 0); var fullmacActual = hmac.HashInner; if (!Utils.ConstantTimeEqual(fullmacActual, 0, ciphertext.Array, ciphertext.Offset + ciphertext.Count - MAC_LENGTH, MAC_LENGTH)) return false; }// using hmac return true; } finally { EtM_CTR.ClearKeyMaterial(); } }// Authenticate()