/// <summary> /// This function uses the asymmetric key specified by the key path /// and encrypts CEK with RSA encryption algorithm. /// </summary> /// <param name="keyPath">Complete path of an asymmetric key in AKV</param> /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param> /// <param name="columnEncryptionKey">Plain text column encryption key</param> /// <returns>Encrypted column encryption key</returns> public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) { // Validate the input parameters ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false); if (null == columnEncryptionKey) { throw SQL.NullColumnEncryptionKey(); } else if (0 == columnEncryptionKey.Length) { throw SQL.EmptyColumnEncryptionKey(); } // Validate encryptionAlgorithm ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false); // CreateCNGProviderWithKey RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false); // Validate whether the key is RSA one or not and then get the key size int keySizeInBytes = GetKeySize(rsaCngProvider); // Construct the encryptedColumnEncryptionKey // Format is // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature // // We currently only support one version byte[] version = new byte[] { _version[0] }; // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant()); byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length); // Encrypt the plain text byte[] cipherText = RSAEncrypt(rsaCngProvider, columnEncryptionKey); byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length); Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size"); // Compute hash // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) byte[] hash; using (SHA256Cng sha256 = new SHA256Cng()) { sha256.TransformBlock(version, 0, version.Length, version, 0); sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0); sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0); sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0); sha256.TransformFinalBlock(cipherText, 0, cipherText.Length); hash = sha256.Hash; } // Sign the hash byte[] signedHash = RSASignHashedData(hash, rsaCngProvider); Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size"); Debug.Assert(RSAVerifySignature(hash, signedHash, rsaCngProvider), @"Invalid signature of the encrypted column encryption key computed."); // Construct the encrypted column encryption key // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length; byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength]; // Copy version byte int currentIndex = 0; Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length); currentIndex += version.Length; // Copy key path length Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length); currentIndex += keyPathLength.Length; // Copy ciphertext length Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length); currentIndex += cipherTextLength.Length; // Copy key path Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length); currentIndex += masterKeyPathBytes.Length; // Copy ciphertext Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length); currentIndex += cipherText.Length; // copy the signature Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length); return encryptedColumnEncryptionKey; }
/// <summary> /// This function uses the asymmetric key specified by the key path /// and decrypts an encrypted CEK with RSA encryption algorithm. /// </summary> /// <param name="masterKeyPath">Complete path of an asymmetric key in CNG</param> /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param> /// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key</param> /// <returns>Plain text column encryption key</returns> public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) { // Validate the input parameters ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true); if (null == encryptedColumnEncryptionKey) { throw SQL.NullEncryptedColumnEncryptionKey(); } if (0 == encryptedColumnEncryptionKey.Length) { throw SQL.EmptyEncryptedColumnEncryptionKey(); } // Validate encryptionAlgorithm ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); // Create RSA Provider with the given CNG name and key name RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true); // Validate whether the key is RSA one or not and then get the key size int keySizeInBytes = GetKeySize(rsaCngProvider); // Validate and decrypt the EncryptedColumnEncryptionKey // Format is // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature // // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and // we will not validate it against the data contained in the CMK metadata (masterKeyPath). // Validate the version byte if (encryptedColumnEncryptionKey[0] != _version[0]) { throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]); } // Get key path length int currentIndex = _version.Length; UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex); currentIndex += sizeof(UInt16); // Get ciphertext length UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex); currentIndex += sizeof(UInt16); // Skip KeyPath // KeyPath exists only for troubleshooting purposes and doesnt need validation. currentIndex += keyPathLength; // validate the ciphertext length if (cipherTextLength != keySizeInBytes) { throw SQL.InvalidCiphertextLengthInEncryptedCEKCng(cipherTextLength, keySizeInBytes, masterKeyPath); } // Validate the signature length // Signature length should be same as the key side for RSA PKCSv1.5 int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength; if (signatureLength != keySizeInBytes) { throw SQL.InvalidSignatureInEncryptedCEKCng(signatureLength, keySizeInBytes, masterKeyPath); } // Get ciphertext byte[] cipherText = new byte[cipherTextLength]; Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length); currentIndex += cipherTextLength; // Get signature byte[] signature = new byte[signatureLength]; Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length); // Compute the hash to validate the signature byte[] hash; using (SHA256Cng sha256 = new SHA256Cng()) { sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length); hash = sha256.Hash; } Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key."); // Validate the signature if (!RSAVerifySignature(hash, signature, rsaCngProvider)) { throw SQL.InvalidSignature(masterKeyPath); } // Decrypt the CEK return RSADecrypt(rsaCngProvider, cipherText); }
/// <summary> /// This function uses a certificate specified by the key path /// and decrypts an encrypted CEK with RSA encryption algorithm. /// </summary> /// <param name="masterKeyPath">Complete path of a certificate</param> /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param> /// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key</param> /// <returns>Plain text column encryption key</returns> public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) { // Validate the input parameters ValidateNonEmptyCertificatePath(masterKeyPath, isSystemOp: true); if (null == encryptedColumnEncryptionKey) { throw SQL.NullEncryptedColumnEncryptionKey(); } else if (0 == encryptedColumnEncryptionKey.Length) { throw SQL.EmptyEncryptedColumnEncryptionKey(); } // Validate encryptionAlgorithm ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); // Validate key path length ValidateCertificatePathLength(masterKeyPath, isSystemOp: true); // Parse the path and get the X509 cert X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: true); int keySizeInBytes = certificate.PublicKey.Key.KeySize / 8; // Validate and decrypt the EncryptedColumnEncryptionKey // Format is // version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature // // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and // we will not validate it against the data contained in the CMK metadata (masterKeyPath). // Validate the version byte if (encryptedColumnEncryptionKey[0] != _version[0]) { throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]); } // Get key path length int currentIndex = _version.Length; Int16 keyPathLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); currentIndex += sizeof(Int16); // Get ciphertext length int cipherTextLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); currentIndex += sizeof(Int16); // Skip KeyPath // KeyPath exists only for troubleshooting purposes and doesnt need validation. currentIndex += keyPathLength; // validate the ciphertext length if (cipherTextLength != keySizeInBytes) { throw SQL.InvalidCiphertextLengthInEncryptedCEK(cipherTextLength, keySizeInBytes, masterKeyPath); } // Validate the signature length int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength; if (signatureLength != keySizeInBytes) { throw SQL.InvalidSignatureInEncryptedCEK(signatureLength, keySizeInBytes, masterKeyPath); } // Get ciphertext byte[] cipherText = new byte[cipherTextLength]; Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength); currentIndex += cipherTextLength; // Get signature byte[] signature = new byte[signatureLength]; Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length); // Compute the hash to validate the signature byte[] hash; using (SHA256Cng sha256 = new SHA256Cng()) { sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length); hash = sha256.Hash; } Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key."); // Validate the signature if (!RSAVerifySignature(hash, signature, certificate)) { throw SQL.InvalidCertificateSignature(masterKeyPath); } // Decrypt the CEK return RSADecrypt(cipherText, certificate); }