/// <summary> /// Decrypts encrypted private key file data. /// </summary> /// <param name="cipherInfo">The cipher info.</param> /// <param name="cipherData">Encrypted data.</param> /// <param name="passPhrase">Decryption pass phrase.</param> /// <param name="binarySalt">Decryption binary salt.</param> /// <returns>Decrypted byte array.</returns> /// <exception cref="System.ArgumentNullException">cipherInfo</exception> /// <exception cref="ArgumentNullException"><paramref name="cipherInfo" />, <paramref name="cipherData" />, <paramref name="passPhrase" /> or <paramref name="binarySalt" /> is null.</exception> private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, string passPhrase, byte[] binarySalt) { if (cipherInfo == null) { throw new ArgumentNullException("cipherInfo"); } if (cipherData == null) { throw new ArgumentNullException("cipherData"); } if (binarySalt == null) { throw new ArgumentNullException("binarySalt"); } List <byte> cipherKey = new List <byte>(); using (var md5 = new MD5Hash()) { var passwordBytes = Encoding.UTF8.GetBytes(passPhrase); // Use 8 bytes binary salkt var initVector = passwordBytes.Concat(binarySalt.Take(8)); var hash = md5.ComputeHash(initVector.ToArray()).AsEnumerable(); cipherKey.AddRange(hash); while (cipherKey.Count < cipherInfo.KeySize / 8) { hash = hash.Concat(initVector); hash = md5.ComputeHash(hash.ToArray()); cipherKey.AddRange(hash); } } var cipher = cipherInfo.Cipher(cipherKey.ToArray(), binarySalt); return(cipher.Decrypt(cipherData)); }
private void Open(Stream privateKey, string passPhrase) { if (privateKey == null) { throw new ArgumentNullException("privateKey"); } Match privateKeyMatch = null; using (StreamReader sr = new StreamReader(privateKey)) { var text = sr.ReadToEnd(); privateKeyMatch = _privateKeyRegex.Match(text); } if (!privateKeyMatch.Success) { throw new SshException("Invalid private key file."); } var keyName = privateKeyMatch.Result("${keyName}"); var cipherName = privateKeyMatch.Result("${cipherName}"); var salt = privateKeyMatch.Result("${salt}"); var data = privateKeyMatch.Result("${data}"); var binaryData = System.Convert.FromBase64String(data); byte[] decryptedData = null; if (!string.IsNullOrEmpty(cipherName) && !string.IsNullOrEmpty(salt)) { if (string.IsNullOrEmpty(passPhrase)) { throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty."); } byte[] binarySalt = new byte[salt.Length / 2]; for (int i = 0; i < binarySalt.Length; i++) { binarySalt[i] = Convert.ToByte(salt.Substring(i * 2, 2), 16); } CipherInfo cipher = null; switch (cipherName) { case "DES-EDE3-CBC": cipher = new CipherInfo(192, (key, iv) => { return(new TripleDesCipher(key, new CbcCipherMode(iv), new PKCS7Padding())); }); break; case "DES-EDE3-CFB": cipher = new CipherInfo(192, (key, iv) => { return(new TripleDesCipher(key, new CfbCipherMode(iv), new PKCS7Padding())); }); break; case "DES-CBC": cipher = new CipherInfo(64, (key, iv) => { return(new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padding())); }); break; case "AES-128-CBC": cipher = new CipherInfo(128, (key, iv) => { return(new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding())); }); break; case "AES-192-CBC": cipher = new CipherInfo(192, (key, iv) => { return(new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding())); }); break; case "AES-256-CBC": cipher = new CipherInfo(256, (key, iv) => { return(new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding())); }); break; default: throw new SshException(string.Format(CultureInfo.CurrentCulture, "Private key cipher \"{0}\" is not supported.", cipherName)); } decryptedData = DecryptKey(cipher, binaryData, passPhrase, binarySalt); } else { decryptedData = binaryData; } switch (keyName) { case "RSA": this._key = new RsaKey(decryptedData.ToArray()); this.HostKey = new KeyHostAlgorithm("ssh-rsa", this._key); break; case "DSA": this._key = new DsaKey(decryptedData.ToArray()); this.HostKey = new KeyHostAlgorithm("ssh-dss", this._key); break; case "SSH2 ENCRYPTED": var reader = new SshDataReader(decryptedData); var magicNumber = reader.ReadUInt32(); if (magicNumber != 0x3f6ff9eb) { throw new SshException("Invalid SSH2 private key."); } var totalLength = reader.ReadUInt32(); // Read total bytes length including magic number var keyType = reader.ReadString(); var ssh2CipherName = reader.ReadString(); var blobSize = (int)reader.ReadUInt32(); byte[] keyData = null; if (ssh2CipherName == "none") { keyData = reader.ReadBytes(blobSize); } //else if (ssh2CipherName == "3des-cbc") //{ // var key = GetCipherKey(passPhrase, 192 / 8); // var ssh2Сipher = new TripleDesCipher(key, null, null); // keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize)); //} else { throw new SshException(string.Format("Cipher method '{0}' is not supported.", cipherName)); } // TODO: Create two specific data types to avoid using SshDataReader class reader = new SshDataReader(keyData); var decryptedLength = reader.ReadUInt32(); if (decryptedLength + 4 != blobSize) { throw new SshException("Invalid passphrase."); } if (keyType == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}") { var exponent = reader.ReadBigIntWithBits(); //e var d = reader.ReadBigIntWithBits(); //d var modulus = reader.ReadBigIntWithBits(); //n var inverseQ = reader.ReadBigIntWithBits(); //u var q = reader.ReadBigIntWithBits(); //p var p = reader.ReadBigIntWithBits(); //q this._key = new RsaKey(modulus, exponent, d, p, q, inverseQ); this.HostKey = new KeyHostAlgorithm("ssh-rsa", this._key); } else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}") { var zero = reader.ReadUInt32(); if (zero != 0) { throw new SshException("Invalid private key"); } var p = reader.ReadBigIntWithBits(); var g = reader.ReadBigIntWithBits(); var q = reader.ReadBigIntWithBits(); var y = reader.ReadBigIntWithBits(); var x = reader.ReadBigIntWithBits(); this._key = new DsaKey(p, q, g, y, x); this.HostKey = new KeyHostAlgorithm("ssh-dss", this._key); } else { throw new NotSupportedException(string.Format("Key type '{0}' is not supported.", keyType)); } break; default: throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supported.", keyName)); } }