e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
187 lines
9.9 KiB
C#
187 lines
9.9 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="NetFXCryptoService.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.Security.Cryptography {
|
|
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
|
|
/******************************************************************
|
|
* !! WARNING !! *
|
|
* This class contains cryptographic code. If you make changes to *
|
|
* this class, please have it reviewed by the appropriate people. *
|
|
******************************************************************/
|
|
|
|
// Uses .NET Framework classes to encrypt (SymmetricAlgorithm) and sign (KeyedHashAlgorithm) data.
|
|
//
|
|
// [PROTECT]
|
|
// INPUT: clearData
|
|
// OUTPUT: protectedData
|
|
// ALGORITHM:
|
|
// protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
|
|
//
|
|
// [UNPROTECT]
|
|
// INPUT: protectedData
|
|
// OUTPUT: clearData
|
|
// ALGORITHM:
|
|
// 1) Assume protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
|
|
// 2) Validate the signature over the payload and strip it from the end
|
|
// 3) Strip off the IV from the beginning of the payload
|
|
// 4) Decrypt what remains of the payload, and return it as clearData
|
|
|
|
internal sealed class NetFXCryptoService : ICryptoService {
|
|
|
|
private readonly ICryptoAlgorithmFactory _cryptoAlgorithmFactory;
|
|
private readonly CryptographicKey _encryptionKey;
|
|
private readonly bool _predictableIV;
|
|
private readonly CryptographicKey _validationKey;
|
|
|
|
public NetFXCryptoService(ICryptoAlgorithmFactory cryptoAlgorithmFactory, CryptographicKey encryptionKey, CryptographicKey validationKey, bool predictableIV = false) {
|
|
_cryptoAlgorithmFactory = cryptoAlgorithmFactory;
|
|
_encryptionKey = encryptionKey;
|
|
_validationKey = validationKey;
|
|
_predictableIV = predictableIV;
|
|
}
|
|
|
|
public byte[] Protect(byte[] clearData) {
|
|
// The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
|
|
checked {
|
|
|
|
// These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
|
|
using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
|
|
// Initialize the algorithm with the specified key and an appropriate IV
|
|
encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();
|
|
|
|
if (_predictableIV) {
|
|
// The caller wanted the output to be predictable (e.g. for caching), so we'll create an
|
|
// appropriate IV directly from the input buffer. The IV length is equal to the block size.
|
|
encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize);
|
|
}
|
|
else {
|
|
// If the caller didn't ask for a predictable IV, just let the algorithm itself choose one.
|
|
encryptionAlgorithm.GenerateIV();
|
|
}
|
|
byte[] iv = encryptionAlgorithm.IV;
|
|
|
|
using (MemoryStream memStream = new MemoryStream()) {
|
|
memStream.Write(iv, 0, iv.Length);
|
|
|
|
// At this point:
|
|
// memStream := IV
|
|
|
|
// Write the encrypted payload to the memory stream.
|
|
using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) {
|
|
using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) {
|
|
cryptoStream.Write(clearData, 0, clearData.Length);
|
|
cryptoStream.FlushFinalBlock();
|
|
|
|
// At this point:
|
|
// memStream := IV || Enc(Kenc, IV, clearData)
|
|
|
|
// These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
|
|
using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
|
|
// Initialize the algorithm with the specified key
|
|
signingAlgorithm.Key = _validationKey.GetKeyMaterial();
|
|
|
|
// Compute the signature
|
|
byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length);
|
|
|
|
// At this point:
|
|
// memStream := IV || Enc(Kenc, IV, clearData)
|
|
// signature := Sign(Kval, IV || Enc(Kenc, IV, clearData))
|
|
|
|
// Append the signature to the encrypted payload
|
|
memStream.Write(signature, 0, signature.Length);
|
|
|
|
// At this point:
|
|
// memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
|
|
|
|
// Algorithm complete
|
|
byte[] protectedData = memStream.ToArray();
|
|
return protectedData;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public byte[] Unprotect(byte[] protectedData) {
|
|
// The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
|
|
checked {
|
|
|
|
// We want to check that the input is in the form:
|
|
// protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
|
|
|
|
// Definitions used in this method:
|
|
// encryptedPayload := Enc(Kenc, IV, clearData)
|
|
// signature := Sign(Kval, IV || encryptedPayload)
|
|
|
|
// These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
|
|
using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
|
|
decryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();
|
|
|
|
// These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
|
|
using (KeyedHashAlgorithm validationAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
|
|
validationAlgorithm.Key = _validationKey.GetKeyMaterial();
|
|
|
|
// First, we need to verify that protectedData is even long enough to contain
|
|
// the required components (IV, encryptedPayload, signature).
|
|
|
|
int ivByteCount = decryptionAlgorithm.BlockSize / 8; // IV length is equal to the block size
|
|
int signatureByteCount = validationAlgorithm.HashSize / 8;
|
|
int encryptedPayloadByteCount = protectedData.Length - ivByteCount - signatureByteCount;
|
|
if (encryptedPayloadByteCount <= 0) {
|
|
// protectedData doesn't meet minimum length requirements
|
|
return null;
|
|
}
|
|
|
|
// If that check passes, we need to detect payload tampering.
|
|
|
|
// Compute the signature over the IV and encrypted payload
|
|
// computedSignature := Sign(Kval, IV || encryptedPayload)
|
|
byte[] computedSignature = validationAlgorithm.ComputeHash(protectedData, 0, ivByteCount + encryptedPayloadByteCount);
|
|
|
|
if (!CryptoUtil.BuffersAreEqual(
|
|
buffer1: protectedData, buffer1Offset: ivByteCount + encryptedPayloadByteCount, buffer1Count: signatureByteCount,
|
|
buffer2: computedSignature, buffer2Offset: 0, buffer2Count: computedSignature.Length)) {
|
|
|
|
// the computed signature didn't match the incoming signature, which is a sign of payload tampering
|
|
return null;
|
|
}
|
|
|
|
// At this point, we're certain that we generated the signature over this payload,
|
|
// so we can go ahead with decryption.
|
|
|
|
// Populate the IV from the incoming stream
|
|
byte[] iv = new byte[ivByteCount];
|
|
Buffer.BlockCopy(protectedData, 0, iv, 0, iv.Length);
|
|
decryptionAlgorithm.IV = iv;
|
|
|
|
// Write the decrypted payload to the memory stream.
|
|
using (MemoryStream memStream = new MemoryStream()) {
|
|
using (ICryptoTransform decryptor = decryptionAlgorithm.CreateDecryptor()) {
|
|
using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write)) {
|
|
cryptoStream.Write(protectedData, ivByteCount, encryptedPayloadByteCount);
|
|
cryptoStream.FlushFinalBlock();
|
|
|
|
// At this point
|
|
// memStream := clearData
|
|
|
|
byte[] clearData = memStream.ToArray();
|
|
return clearData;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|