public YubiCryptEngine(Assembly callingAssembly) { List <Assembly> assemblies = new List <Assembly>(); if (callingAssembly != null) { assemblies.Add(callingAssembly); } assemblies.Add(typeof(YubiCryptEngine).GetTypeInfo().Assembly); var configuration = new ContainerConfiguration().WithAssemblies(assemblies); using (var container = configuration.CreateContainer()) { SymmetricCipherProviders = container.GetExports <ISymmetricCipherProvider>(); KeyDerivationFunctionProviders = container.GetExports <ITwoFactorKeyDerivationFunctionProvider>(); MACProviders = container.GetExports <IMACProvider>(); } #if !DEBUG if (!KeyDerivationFunctionProviders.Any()) { throw new CompositionFailedException("No Key Derivation Function providers were found."); } if (!SymmetricCipherProviders.Any()) { throw new CompositionFailedException("No Symmetric Cipher providers were found."); } if (!MACProviders.Any()) { throw new CompositionFailedException("No Message Authentication Code providers were found."); } #endif KeyDerivationFunctionProviders = KeyDerivationFunctionProviders.OrderBy(p => p.Name); SymmetricCipherProviders = SymmetricCipherProviders.OrderBy(p => p.Name); MACProviders = MACProviders.OrderBy(p => p.Name); }
// Why accept an outputStream instead of returning a Stream? Flexibility. // Client apps can write the encrypted output directly to an Network Stream, a File, a Memory Stream, etc // It's up to the client implementation where to direct the output public void EncryptFile(Stream inputFileStream, Stream outputFileStream, string passphrase, CipherSuite cipherSuite) { //Check if we support this cipher suite ITwoFactorKeyDerivationFunctionProvider twoFactorKeyDerivationProvider = KeyDerivationFunctionProviders.Where(p => p.IDByte == cipherSuite.KeyDerivationFunctionIDByte).SingleOrDefault(); ISymmetricCipherProvider symmetricCipherProvider = SymmetricCipherProviders.Where(p => p.IDByte == cipherSuite.SymmetricCipherIDByte).SingleOrDefault(); IMACProvider macProvider = MACProviders.Where(p => p.IDByte == cipherSuite.MACIDByte).SingleOrDefault(); if (twoFactorKeyDerivationProvider == null) { throw new Exception("Unsupported Key Derivation Function with ID byte " + cipherSuite.KeyDerivationFunctionIDByte); } if (symmetricCipherProvider == null) { throw new Exception("Unsupported Symmetric Cipher with ID byte " + cipherSuite.SymmetricCipherIDByte); } if (macProvider == null) { throw new Exception("Unsupported Message Authentication Code with ID byte " + cipherSuite.MACIDByte); } Random randomGen = new Random(); var salt = new byte[32]; randomGen.NextBytes(salt); //random salt (RNG doesn't have to be cryptographically strong) var passphraseBytes = Encoding.UTF8.GetBytes(passphrase); byte[] encryptionKey = new byte[symmetricCipherProvider.KeySize / 8]; byte[] hmacKey = new byte[symmetricCipherProvider.KeySize / 8]; byte[] iv = new byte[symmetricCipherProvider.BlockSize / 8]; twoFactorKeyDerivationProvider.DeriveHMACAndEncryptionKey(passphraseBytes, salt, ref hmacKey, ref encryptionKey); randomGen.NextBytes(iv); var tokenSerialNumberBytes = twoFactorKeyDerivationProvider.GetExternalTokenSerial(); var inputValidation = Combine(passphraseBytes, tokenSerialNumberBytes); var inputValidationHash = CalculateInputValidation(inputValidation); //Write Header var headerStream = new BinaryWriter(outputFileStream); //Header: YCF1 (4bytes) + Cipher Suite (4bytes) + Salt Seed (32bytes) + IV (16bytes) + Iterations (4bytes) + Input Validation (32bytes) headerStream.Write(YC_FILE_SPEC_BYTES); headerStream.Write(YC_FILE_SPEC_VERSION); headerStream.Write(IdBytesFromCipherSuite(cipherSuite)); headerStream.Write(salt); headerStream.Write(iv); headerStream.Write((Int32)twoFactorKeyDerivationProvider.NumberOfOterations); headerStream.Write(inputValidationHash); symmetricCipherProvider.Encrypt(inputFileStream, outputFileStream, encryptionKey, iv); var streamHMAC = macProvider.CalculateMAC(outputFileStream, hmacKey); headerStream.Write(streamHMAC); }
public void DecryptFile(Stream inputFileStream, Stream outputFileStream, string passphrase) { if (inputFileStream.Position != 0) { if (inputFileStream.CanSeek) { inputFileStream.Seek(0, SeekOrigin.Begin); } else { throw new ArgumentException("Unable to seek inputFileStream to the beggining."); } } using (var headerStream = new BinaryReader(inputFileStream)) { var fileSpecMagicBytes = headerStream.ReadBytes(3); if (!fileSpecMagicBytes.SequenceEqual(YC_FILE_SPEC_BYTES)) { throw new Exception("Wrong file header"); } var fileSpecVersion = headerStream.ReadByte(); if (fileSpecVersion != YC_FILE_SPEC_VERSION) { throw new Exception("Invalid YubiCrypt file version."); } var cipherSuiteBytes = headerStream.ReadBytes(4); var cipherSuite = CipherSuiteFromIdBytes(cipherSuiteBytes); var cipherSuiteKDF = KeyDerivationFunctionProviders.Where(p => p.IDByte == cipherSuite.KeyDerivationFunctionIDByte).SingleOrDefault(); if (cipherSuiteKDF == null) { throw new Exception("Unsupported Key Derivation Function with ID byte " + cipherSuite.KeyDerivationFunctionIDByte); } var cipherSuiteSymmetricCipher = SymmetricCipherProviders.Where(p => p.IDByte == cipherSuite.SymmetricCipherIDByte).SingleOrDefault(); if (cipherSuiteSymmetricCipher == null) { throw new Exception("Unsupported Key Derivation Function with ID byte " + cipherSuite.SymmetricCipherIDByte); } var cipherSuiteMAC = MACProviders.Where(p => p.IDByte == cipherSuite.MACIDByte).SingleOrDefault(); if (cipherSuiteMAC == null) { throw new Exception("Unsupported Message Authentication Code with ID byte " + cipherSuite.MACIDByte); } var passphraseBytes = Encoding.UTF8.GetBytes(passphrase); var salt = headerStream.ReadBytes(32); var iv = headerStream.ReadBytes(16); var iterations = headerStream.ReadInt32(); var inputValidation = headerStream.ReadBytes(32); var tokenSerialNumberBytes = cipherSuiteKDF.GetExternalTokenSerial(); var inputValidationInput = Combine(passphraseBytes, tokenSerialNumberBytes); var inputValidationHash = CalculateInputValidation(inputValidationInput); if (!inputValidationHash.SequenceEqual(inputValidation)) { throw new Exception("Invalid credentials. Check if you are entering the correct passphrase and using the correct Yubikey token."); } byte[] encryptionKey = new byte[cipherSuiteSymmetricCipher.KeySize / 8]; byte[] hmacKey = new byte[cipherSuiteSymmetricCipher.KeySize / 8]; cipherSuiteKDF.NumberOfOterations = iterations; cipherSuiteKDF.DeriveHMACAndEncryptionKey(passphraseBytes, salt, ref hmacKey, ref encryptionKey); //Check file HMAC if (!cipherSuiteMAC.ValidateMAC(inputFileStream, hmacKey)) { throw new Exception("Invalid HMAC."); } //Header is 92 bytes long inputFileStream.Seek(92, SeekOrigin.Begin); cipherSuiteSymmetricCipher.Decrypt(inputFileStream, outputFileStream, encryptionKey, iv); } }