/// <summary> /// Verifies request arguments against a signature using a shared <see cref="SymmetricKey" /> /// and the current time. /// </summary> /// <param name="sharedKey">The shared <see cref="SymmetricKey" />.</param> /// <param name="signature">The base-64 encoded signature.</param> /// <param name="args">The request argument collection.</param> /// <param name="signatureKey"> /// The name of the signature key within the event arguments or <c>null</c> /// if the signature is not present. /// </param> /// <param name="graceInterval">Specifies the time period used for verifying the signature request time.</param> /// <exception cref="SecurityException">Thrown if the signature could not be verified.</exception> /// <remarks> /// <para> /// <paramref name="graceInterval" /> is used when comparing the request time (UTC) embedded /// in the signature with the current machine time (UTC). Request times within the range of: /// </para> /// <code language="none"> /// DateTime.UtcNow - graceInterval <= requestTime <= DateTime.UtcNow + graceInterval /// </code> /// <para> /// will be considered to be valid. Request times outside this range will be invalid. /// Larger grace intervals provide help avoid the problems of incorrect system times or /// long request delivery delays but at the price of the increased exposure to replay /// attacks. /// </para> /// <note> /// If the signature is present in the arguments, then the <paramref name="signatureKey" /> <b>must</b> /// be passed as its name or else the verification will always fail. The reason for this is /// that the hash was originally computed before the signature was added so the hash will be /// different if it includes the signature. /// </note> /// </remarks> public static void Verify(SymmetricKey sharedKey, string signature, ArgCollection args, string signatureKey, TimeSpan graceInterval) { if (!TryVerify(sharedKey, signature, args, signatureKey, graceInterval)) { throw new SecurityException("Access denied."); } }
/// <summary> /// Extends <see cref="ArgCollection" /> by adding a method to retrieve an encrypted string value. /// </summary> /// <param name="args">The current collection.</param> /// <param name="name">The argument name.</param> /// <param name="key">The symmetric encryption key.</param> /// <returns>The decrypted value.</returns> public static string GetEncrypted(this ArgCollection args, string name, SymmetricKey key) { var encrypted = args.Get(name, (byte[])null); if (encrypted == null) { return(null); } return(Crypto.DecryptStringWithSalt8(encrypted, key)); }
/// <summary> /// Generates a request signature using a shared <see cref="SymmetricKey" /> /// for request arguments as well as the current time. /// </summary> /// <param name="sharedKey">The shared <see cref="SymmetricKey" />.</param> /// <param name="args">The request argument collection.</param> /// <returns>The base-64 encoded signature.</returns> public static string Generate(SymmetricKey sharedKey, ArgCollection args) { using (var ms = new EnhancedMemoryStream(512)) { ms.WriteBytesNoLen(Crypto.GetSalt8()); ms.WriteInt32(Magic); ms.WriteInt64(DateTime.UtcNow.Ticks); ms.WriteBytesNoLen(ComputeHash(args, null)); return(Convert.ToBase64String(Crypto.Encrypt(ms.ToArray(), sharedKey))); } }
/// <summary> /// Encrypts a byte array using a combination of an asymmetric RSA key and the /// specified symmetric encryption algorithm and a one-time key generated by /// the method. /// </summary> /// <param name="rsaKey">The encrypting RSA key as XML or as a secure key container name.</param> /// <param name="plainText">The data to be encrypted.</param> /// <param name="algorithm">The symmetric encryption algorithm name.</param> /// <param name="keySize">The one-time symmetric key size to generate in bits.</param> /// <param name="paddedSize">Specifies the minimum padded size of the encrypted content.</param> /// <returns>The encrypted result.</returns> /// <remarks> /// <para> /// The current supported cross platform encryption algorithms /// are: "DES", "RC2", "TripleDES", and "AES" (Rijndael). /// </para> /// </remarks> /// <exception cref="ArgumentException">Thrown if the requested encryption algorithm is unknown.</exception> public static byte[] Encrypt(string rsaKey, byte[] plainText, string algorithm, int keySize, int paddedSize) { SymmetricKey symmetricKey = null; try { return(Encrypt(rsaKey, plainText, algorithm, keySize, paddedSize, out symmetricKey)); } finally { if (symmetricKey != null) { symmetricKey.Dispose(); } } }
/// <summary> /// Decrypts a byte array encrypted using <see cref="Encrypt(string,byte[],string,int,int,out SymmetricKey)" />. /// </summary> /// <param name="rsaKey">The decrypting RSA key as XML or as a secure key container name.</param> /// <param name="cipherText">The encrypted data.</param> /// <returns>The decrypted data.</returns> /// <exception cref="CryptographicException">Thrown is the encrypted data block is incorrectly formatted.</exception> public static byte[] Decrypt(string rsaKey, byte[] cipherText) { SymmetricKey symmetricKey = null; try { return(Decrypt(rsaKey, cipherText, out symmetricKey)); } finally { if (symmetricKey != null) { symmetricKey.Dispose(); } } }
/// <summary> /// Decrypts data encrypted using <see cref="Encrypt(SymmetricKey,byte[],int)" />. /// </summary> /// <param name="symmetricKey">The symmetric algorithm arguments.</param> /// <param name="cipherText">The encrypted data.</param> /// <returns>The decrypted result.</returns> public static byte[] Decrypt(SymmetricKey symmetricKey, byte[] cipherText) { EnhancedMemoryStream input = new EnhancedMemoryStream(cipherText); EnhancedMemoryStream ms = new EnhancedMemoryStream(cipherText.Length); BlockDecryptor decryptor = null; try { // Read the header fields if (input.ReadInt32() != Magic) { throw new CryptographicException(BadFormatMsg); } if (input.ReadInt32() != 0) { throw new CryptographicException("Unsupported secure data format version."); } decryptor = new BlockDecryptor(symmetricKey); // Decrypt the contents ms.WriteBytesNoLen(decryptor.Decrypt(input.ReadBytes32())); ms.Position = 0; if (ms.ReadInt32() != Magic) { throw new CryptographicException("Secure data content is corrupt."); } ms.Position += 8; // Skip over the salt return(ms.ReadBytes32()); } finally { if (decryptor != null) { decryptor.Dispose(); } input.Close(); ms.Close(); } }
/// <summary> /// Encrypts the keys using the symmetric key passed. /// </summary> /// <returns>The encrypted key chain.</returns> public byte[] Encrypt(SymmetricKey key) { using (var ms = new EnhancedMemoryStream()) { ms.WriteInt32(Magic); ms.WriteInt32(keys.Count); foreach (string privateKey in keys.Values) { ms.WriteString16(privateKey); } ms.WriteBytesNoLen(Crypto.GetSalt8()); return(Crypto.Encrypt(ms.ToArray(), key)); } }
/// <summary> /// Used by an issuer to construct a ticket by decrypting one from a byte array. /// </summary> /// <param name="key">The symmetric encryption key.</param> /// <param name="encrypted">The encrypted ticket.</param> /// <exception cref="CryptographicException">Thrown if the ticket is improperly formatted or has been tampered with.</exception> /// <remarks> /// The <see cref="ClientExpirationUtc" /> property will be computed by adding /// <see cref="Lifespan" /> to the local UTC time. /// </remarks> public SecureTicket(SymmetricKey key, byte[] encrypted) { try { int pos; pos = 0; encrypted = Helper.ReadBytes16(encrypted, ref pos); args = ArgCollection.Parse(Crypto.DecryptStringWithSalt8(encrypted, key), '=', '\t'); clientExpirationUtc = DateTime.UtcNow + this.Lifespan; } catch (Exception e) { throw new CryptographicException("Invalid ticket", e); } }
/// <summary> /// Verifies request arguments against a signature using a shared <see cref="SymmetricKey" /> /// and the current time. /// </summary> /// <param name="sharedKey">The shared <see cref="SymmetricKey" />.</param> /// <param name="signature">The base-64 encoded signature.</param> /// <param name="args">The request argument collection.</param> /// <param name="signatureKey"> /// The name of the signature key within the event arguments or <c>null</c> /// if the signature is not present. /// </param> /// <param name="graceInterval">Specifies the time period used for verifying the signature request time.</param> /// <returns><c>true</c> if the signature is valid.</returns> /// <remarks> /// <para> /// <paramref name="graceInterval" /> is used when comparing the request time (UTC) embedded /// in the signature with the current machine time (UTC). Request times within the range of: /// </para> /// <code language="none"> /// DateTime.UtcNow - graceInterval <= requestTime <= DateTime.UtcNow + graceInterval /// </code> /// <para> /// will be considered to be valid. Request times outside this range will be invalid. /// Larger grace intervals provide help avoid the problems of incorrect system times or /// long request delivery delays but at the price of the increased exposure to replay /// attacks. /// </para> /// <note> /// If the signature is present in the arguments, then the <paramref name="signatureKey" /> <b>must</b> /// be passed as its name or else the verification will always fail. The reason for this is /// that the hash was originally computed before the signature was added so the hash will be /// different if it includes the signature. /// </note> /// </remarks> public static bool TryVerify(SymmetricKey sharedKey, string signature, ArgCollection args, string signatureKey, TimeSpan graceInterval) { try { byte[] encrypted = Convert.FromBase64String(signature); byte[] decrypted; byte[] requestHash; byte[] hash; DateTime requestTime; DateTime now; decrypted = Crypto.Decrypt(encrypted, sharedKey); if (decrypted.Length != Size) { return(false); } using (var ms = new EnhancedMemoryStream(decrypted)) { ms.Position = 8; if (ms.ReadInt32() != Magic) { return(false); } requestTime = new DateTime(ms.ReadInt64()); requestHash = ms.ReadBytes(SHA1Hasher.DigestSize); } now = DateTime.UtcNow; if (!Helper.Within(requestTime, now, graceInterval)) { return(false); } hash = ComputeHash(args, signatureKey); return(Helper.ArrayEquals(requestHash, hash)); } catch { return(false); } }
/// <summary> /// Returns a deep clone of the key. /// </summary> /// <returns>The cloned <see cref="SymmetricKey" />.</returns> /// <remarks> /// This method is useful in situations where a copy of a key needs /// to be made before the original key is disposed and its /// <see cref="Key" /> and <see cref="IV" /> properties are zeroed. /// </remarks> public SymmetricKey Clone() { var clone = new SymmetricKey(); clone.Algorithm = this.Algorithm; if (this.Key != null) { clone.Key = new byte[this.Key.Length]; Array.Copy(this.Key, clone.Key, this.Key.Length); } if (this.IV != null) { clone.IV = new byte[this.IV.Length]; Array.Copy(this.IV, clone.IV, this.IV.Length); } return(clone); }
/// <summary> /// Encrypts the ticket using a symmetric key. /// </summary> /// <param name="key">The symmetric encryption key.</param> /// <returns>The encrypted ticket.</returns> public byte[] ToArray(SymmetricKey key) { byte[] privateData = Crypto.EncryptStringWithSalt8(args.ToString(), key); ArgCollection publicArgs = new ArgCollection('=', '\t'); byte[] publicData; byte[] encrypted; int pos; publicArgs.Set("_resource", this.Resource); publicArgs.Set("_lifespan", this.Lifespan); publicData = Helper.ToUTF8(publicArgs.ToString()); encrypted = new byte[2 + privateData.Length + 2 + publicData.Length]; pos = 0; Helper.WriteBytes16(encrypted, ref pos, privateData); Helper.WriteBytes16(encrypted, ref pos, publicData); return(encrypted); }
/// <summary> /// Performs a secure symmetric encryption including cryptographic salt, padding, and /// data validation. /// </summary> /// <param name="symmetricKey">The symmetric algorithm arguments.</param> /// <param name="plainText">The unencrypted data.</param> /// <param name="paddedSize">Specifies the minimum padded size of the encrypted content.</param> /// <returns>The encrypted result.</returns> public static byte[] Encrypt(SymmetricKey symmetricKey, byte[] plainText, int paddedSize) { EnhancedMemoryStream output = new EnhancedMemoryStream(Math.Max(plainText.Length, paddedSize) + 512); EnhancedMemoryStream ms = new EnhancedMemoryStream(512); BlockEncryptor encryptor = new BlockEncryptor(symmetricKey); try { // Write header fields output.WriteInt32(Magic); output.WriteInt32(0); // Write encrypted contents ms.WriteInt32(Magic); ms.WriteBytesNoLen(Crypto.GetSalt8()); ms.WriteBytes32(plainText); for (int i = plainText.Length; i < paddedSize; i++) { ms.WriteByte((byte)i); // Padding bytes } output.WriteBytes32(encryptor.Encrypt(ms.ToArray())); // That's it, we're done. return(output.ToArray()); } finally { if (encryptor != null) { encryptor.Dispose(); } output.Close(); ms.Close(); } }
/// <summary> /// Initializes the encryptor to use the specified /// <see cref="SymmetricKey" />. /// </summary> /// <param name="symmetricKey">The symmetric key.</param> public BlockDecryptor(SymmetricKey symmetricKey) : this(symmetricKey.Algorithm, symmetricKey.Key, symmetricKey.IV) { }
/// <summary> /// Encrypts the ticket using a combination of a symmetric key. /// </summary> /// <param name="key">The symmetric encryption key.</param> /// <returns>The base-64 encoded string of the encrypted ticket.</returns> public string ToBase64String(SymmetricKey key) { return(Convert.ToBase64String(ToArray(key))); }
/// <summary> /// Used by an issuer to construct a ticket by decrypting one from a base 64 encoded byte array. /// </summary> /// <param name="key">The symmetric encryption key.</param> /// <param name="base64Encrypted">The base 64 encoded encrypted ticket.</param> /// <exception cref="CryptographicException">Thrown if the ticket is improperly formatted or has been tampered with.</exception> public SecureTicket(SymmetricKey key, string base64Encrypted) : this(key, Convert.FromBase64String(base64Encrypted)) { }
/// <summary> /// Encrypts a byte array using the specified <see cref="SymmetricKey" />. /// </summary> /// <param name="input">The clear text array.</param> /// <param name="symmetricKey">The symmetric key.</param> /// <returns>The encrypted output.</returns> public static byte[] Encrypt(byte[] input, SymmetricKey symmetricKey) { return(Encrypt(input, symmetricKey.Algorithm, symmetricKey.Key, symmetricKey.IV)); }
/// <summary> /// Used by issues to decrypt and parse a ticket from a base-64 encoded byte array. /// </summary> /// <param name="key">The symmetric encryption key.</param> /// <param name="base64Encrypted">The base 64 encoded encrypted ticket.</param> /// <returns>The decrypted ticket.</returns> /// <exception cref="CryptographicException">Thrown if the ticket is improperly formatted or has been tampered with.</exception> public static SecureTicket Parse(SymmetricKey key, string base64Encrypted) { return(new SecureTicket(key, base64Encrypted)); }
//--------------------------------------------------------------------- // Static members /// <summary> /// Used by issuers to decrypt and parse a ticket from a byte array. /// </summary> /// <param name="key">The symmetric encryption key.</param> /// <param name="encrypted">The encrypted ticket.</param> /// <returns>The decrypted ticket.</returns> /// <exception cref="CryptographicException">Thrown if the ticket is improperly formatted or has been tampered with.</exception> public static SecureTicket Parse(SymmetricKey key, byte[] encrypted) { return(new SecureTicket(key, encrypted)); }
/// <summary> /// Encrypts a byte array with four bytes of cryptogrpahic salt /// using the specified <see cref="SymmetricKey" />. /// </summary> /// <param name="input">The encrypted array.</param> /// <param name="symmetricKey">The symmetric key.</param> /// <returns>The encrypted output.</returns> public static byte[] DecryptWithSalt4(byte[] input, SymmetricKey symmetricKey) { return(DecryptWithSalt4(input, symmetricKey.Algorithm, symmetricKey.Key, symmetricKey.IV)); }
/// <summary> /// Extends <see cref="ArgCollection" /> by adding a method to set an encrypted string value. /// </summary> /// <param name="args">The current collection.</param> /// <param name="name">The argument name.</param> /// <param name="value">The plain text value.</param> /// <param name="key">The symmetric encryption key.</param> public static void SetEncrypted(this ArgCollection args, string name, string value, SymmetricKey key) { if (value == null) { args.Set(name, (byte[])null); } else { args.Set(name, Crypto.EncryptStringWithSalt8(value, key)); } }
/// <summary> /// Encrypts a string with eight bytes of cryptogrpahic salt /// using the specified <see cref="SymmetricKey" />. /// </summary> /// <param name="input">The clear text string.</param> /// <param name="symmetricKey">The symmetric key.</param> /// <returns>The encrypted output.</returns> public static byte[] EncryptStringWithSalt8(string input, SymmetricKey symmetricKey) { return(EncryptStringWithSalt8(input, symmetricKey.Algorithm, symmetricKey.Key, symmetricKey.IV)); }
/// <summary> /// Decrypts a string with eight bytes of cryptogrpahic salt using /// the specified <see cref="SymmetricKey" />. /// </summary> /// <param name="input">The encrypted string.</param> /// <param name="symmetricKey">The symmetric key.</param> /// <returns>The encrypted output.</returns> public static string DecryptStringWithSalt8(byte[] input, SymmetricKey symmetricKey) { return(DecryptStringWithSalt8(input, symmetricKey.Algorithm, symmetricKey.Key, symmetricKey.IV)); }
/// <summary> /// Initializes the decryptor to use the specified /// <see cref="SymmetricKey" />. /// </summary> /// <param name="symmetricKey">The symmetric key.</param> public StreamDecryptor(SymmetricKey symmetricKey) : this(symmetricKey.Algorithm, symmetricKey.Key, symmetricKey.IV) { }
/// <summary> /// Static constructor. /// </summary> static SymmetricKey() { SymmetricKey.PlainText = new SymmetricKey(); }
/// <summary> /// Encrypts a byte array using a combination of an asymmetric RSA key and the /// specified symmetric encryption algorithm and a one-time key generated by /// the method. /// </summary> /// <param name="rsaKey">The encrypting RSA key as XML or as a secure key container name.</param> /// <param name="plainText">The data to be encrypted.</param> /// <param name="algorithm">The symmetric encryption algorithm name.</param> /// <param name="keySize">The one-time symmetric key size to generate in bits.</param> /// <param name="paddedSize">Specifies the minimum padded size of the encrypted content.</param> /// <param name="symmetricKey">Returns as the symmetric encryption algorithm arguments.</param> /// <returns>The encrypted result.</returns> /// <remarks> /// <para> /// Note that applications should take some care to ensure that the <paramref name="symmetricKey" /> /// value return is disposed so that the symmetric encryption key will be cleared. /// </para> /// <para> /// The current supported cross platform encryption algorithms /// are: "DES", "RC2", "TripleDES", and "AES" (Rijndael). /// </para> /// </remarks> /// <exception cref="ArgumentException">Thrown if the requested encryption algorithm is unknown.</exception> public static byte[] Encrypt(string rsaKey, byte[] plainText, string algorithm, int keySize, int paddedSize, out SymmetricKey symmetricKey) { EnhancedMemoryStream output = new EnhancedMemoryStream(Math.Max(plainText.Length, paddedSize) + 512); EnhancedMemoryStream ms = new EnhancedMemoryStream(512); BlockEncryptor encryptor = null; byte[] symKey; byte[] symIV; Crypto.GenerateSymmetricKey(algorithm, keySize, out symKey, out symIV); encryptor = new BlockEncryptor(algorithm, symKey, symIV); symmetricKey = new SymmetricKey(algorithm, (byte[])symKey.Clone(), (byte[])symIV.Clone()); try { // Write header fields output.WriteInt32(Magic); output.WriteInt32(0); // Write encryption Info ms.WriteString16(algorithm); ms.WriteBytes16(symKey); ms.WriteBytes16(symIV); ms.WriteBytesNoLen(Crypto.GetSalt8()); output.WriteBytes16(AsymmetricCrypto.Encrypt(CryptoAlgorithm.RSA, rsaKey, ms.ToArray())); // Write encrypted contents ms.SetLength(0); ms.WriteInt32(Magic); ms.WriteBytesNoLen(Crypto.GetSalt8()); ms.WriteBytes32(plainText); for (int i = plainText.Length; i < paddedSize; i++) { ms.WriteByte((byte)i); // Padding bytes } output.WriteBytes32(encryptor.Encrypt(ms.ToArray())); // That's it, we're done. return(output.ToArray()); } finally { if (symKey != null) { Array.Clear(symKey, 0, symKey.Length); } if (symIV != null) { Array.Clear(symIV, 0, symIV.Length); } if (encryptor != null) { encryptor.Dispose(); } output.Close(); ms.Close(); } }
/// <summary> /// Decrypts a byte array encrypted using <see cref="Encrypt(string ,byte[],string,int,int,out SymmetricKey)" />. /// </summary> /// <param name="rsaKey">The decrypting RSA key as XML or as a secure key container name.</param> /// <param name="cipherText">The encrypted data.</param> /// <param name="symmetricKey">Returns as the symmetric encryption algorithm arguments.</param> /// <returns>The decrypted data.</returns> /// <exception cref="CryptographicException">Thrown is the encrypted data block is incorrectly formatted.</exception> /// <remarks> /// Note that applications should take some care to ensure that the <paramref name="symmetricKey" /> /// value return is disposed so that the symmetric encryption key will be cleared. /// </remarks> public static byte[] Decrypt(string rsaKey, byte[] cipherText, out SymmetricKey symmetricKey) { EnhancedMemoryStream input = new EnhancedMemoryStream(cipherText); EnhancedMemoryStream ms = new EnhancedMemoryStream(cipherText.Length); BlockDecryptor decryptor = null; byte[] symKey; byte[] symIV; string algorithm; try { // Read the header fields if (input.ReadInt32() != Magic) { throw new CryptographicException(BadFormatMsg); } if (input.ReadInt32() != 0) { throw new CryptographicException("Unsupported secure data format version."); } // Decrypt the encryption info ms.WriteBytesNoLen(AsymmetricCrypto.Decrypt(CryptoAlgorithm.RSA, rsaKey, input.ReadBytes16())); ms.Position = 0; algorithm = ms.ReadString16(); symKey = ms.ReadBytes16(); symIV = ms.ReadBytes16(); symmetricKey = new SymmetricKey(algorithm, symKey, symIV); decryptor = new BlockDecryptor(algorithm, symKey, symIV); // Decrypt the contents ms.SetLength(0); ms.WriteBytesNoLen(decryptor.Decrypt(input.ReadBytes32())); ms.Position = 0; if (ms.ReadInt32() != Magic) { throw new CryptographicException("Secure data content is corrupt."); } ms.Position += 8; // Skip over the salt return(ms.ReadBytes32()); } finally { if (decryptor != null) { decryptor.Dispose(); } input.Close(); ms.Close(); } }