@@ -39,7 +39,7 @@ namespace Renci.SshNet
3939 /// </list>
4040 /// </para>
4141 /// <para>
42- /// The following encryption algorithms are supported:
42+ /// The following encryption algorithms are supported for OpenSSL PEM and ssh.com format :
4343 /// <list type="bullet">
4444 /// <item>
4545 /// <description>DES-EDE3-CBC</description>
@@ -60,6 +60,39 @@ namespace Renci.SshNet
6060 /// <description>AES-256-CBC</description>
6161 /// </item>
6262 /// </list>
63+ /// The following encryption algorithms are supported for OpenSSH format:
64+ /// <list type="bullet">
65+ /// <item>
66+ /// <description>3des-cbc</description>
67+ /// </item>
68+ /// <item>
69+ /// <description>aes128-cbc</description>
70+ /// </item>
71+ /// <item>
72+ /// <description>aes192-cbc</description>
73+ /// </item>
74+ /// <item>
75+ /// <description>aes256-cbc</description>
76+ /// </item>
77+ /// <item>
78+ /// <description>aes128-ctr</description>
79+ /// </item>
80+ /// <item>
81+ /// <description>aes192-ctr</description>
82+ /// </item>
83+ /// <item>
84+ /// <description>aes256-ctr</description>
85+ /// </item>
86+ /// <item>
87+ /// <description>[email protected] </description> 88+ /// </item>
89+ /// <item>
90+ /// <description>[email protected] </description> 91+ /// </item>
92+ /// <item>
93+ /// <description>[email protected] </description> 94+ /// </item>
95+ /// </list>
6396 /// </para>
6497 /// </remarks>
6598 public partial class PrivateKeyFile : IPrivateKeySource , IDisposable
@@ -450,7 +483,17 @@ private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, strin
450483
451484 var cipher = cipherInfo . Cipher ( cipherKey . ToArray ( ) , binarySalt ) ;
452485
453- return cipher . Decrypt ( cipherData ) ;
486+ try
487+ {
488+ return cipher . Decrypt ( cipherData ) ;
489+ }
490+ finally
491+ {
492+ if ( cipher is IDisposable disposable )
493+ {
494+ disposable . Dispose ( ) ;
495+ }
496+ }
454497 }
455498
456499 /// <summary>
@@ -474,7 +517,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
474517 throw new SshException ( "This openssh key does not contain the 'openssh-key-v1' format magic header" ) ;
475518 }
476519
477- // cipher will be "aes256-cbc" if using a passphrase, "none" otherwise
520+ // cipher will be "aes256-cbc" or other cipher if using a passphrase, "none" otherwise
478521 var cipherName = keyReader . ReadString ( Encoding . UTF8 ) ;
479522
480523 // key derivation function (kdf): bcrypt or nothing
@@ -503,7 +546,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
503546
504547 // possibly encrypted private key
505548 var privateKeyLength = ( int ) keyReader . ReadUInt32 ( ) ;
506- var privateKeyBytes = keyReader . ReadBytes ( privateKeyLength ) ;
549+ byte [ ] privateKeyBytes ;
507550
508551 // decrypt private key if necessary
509552 if ( cipherName != "none" )
@@ -518,38 +561,76 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
518561 throw new SshException ( "kdf " + kdfName + " is not supported for openssh key file" ) ;
519562 }
520563
521- // inspired by the SSHj library (https://github.com/hierynomus/sshj)
522- // apply the kdf to derive a key and iv from the passphrase
523- var passPhraseBytes = Encoding . UTF8 . GetBytes ( passPhrase ) ;
524- var keyiv = new byte [ 48 ] ;
525- new BCrypt ( ) . Pbkdf ( passPhraseBytes , salt , rounds , keyiv ) ;
526- var key = new byte [ 32 ] ;
527- Array . Copy ( keyiv , 0 , key , 0 , 32 ) ;
528- var iv = new byte [ 16 ] ;
529- Array . Copy ( keyiv , 32 , iv , 0 , 16 ) ;
530-
531- AesCipher cipher ;
564+ var ivLength = 16 ;
565+ CipherInfo cipherInfo ;
532566 switch ( cipherName )
533567 {
568+ case "3des-cbc" :
569+ ivLength = 8 ;
570+ cipherInfo = new CipherInfo ( 192 , ( key , iv ) => new TripleDesCipher ( key , new CbcCipherMode ( iv ) , padding : null ) ) ;
571+ break ;
572+ case "aes128-cbc" :
573+ cipherInfo = new CipherInfo ( 128 , ( key , iv ) => new AesCipher ( key , iv , AesCipherMode . CBC , pkcs7Padding : false ) ) ;
574+ break ;
575+ case "aes192-cbc" :
576+ cipherInfo = new CipherInfo ( 192 , ( key , iv ) => new AesCipher ( key , iv , AesCipherMode . CBC , pkcs7Padding : false ) ) ;
577+ break ;
534578 case "aes256-cbc" :
535- cipher = new AesCipher ( key , iv , AesCipherMode . CBC , pkcs7Padding : false ) ;
579+ cipherInfo = new CipherInfo ( 256 , ( key , iv ) => new AesCipher ( key , iv , AesCipherMode . CBC , pkcs7Padding : false ) ) ;
580+ break ;
581+ case "aes128-ctr" :
582+ cipherInfo = new CipherInfo ( 128 , ( key , iv ) => new AesCipher ( key , iv , AesCipherMode . CTR , pkcs7Padding : false ) ) ;
583+ break ;
584+ case "aes192-ctr" :
585+ cipherInfo = new CipherInfo ( 192 , ( key , iv ) => new AesCipher ( key , iv , AesCipherMode . CTR , pkcs7Padding : false ) ) ;
536586 break ;
537587 case "aes256-ctr" :
538- cipher = new AesCipher ( key , iv , AesCipherMode . CTR , pkcs7Padding : false ) ;
588+ cipherInfo = new CipherInfo ( 256 , ( key , iv ) => new AesCipher ( key , iv , AesCipherMode . CTR , pkcs7Padding : false ) ) ;
589+ break ;
590+ 591+ cipherInfo = new CipherInfo ( 128 , ( key , iv ) => new AesGcmCipher ( key , iv , aadLength : 0 ) , isAead : true ) ;
592+ break ;
593+ 594+ cipherInfo = new CipherInfo ( 256 , ( key , iv ) => new AesGcmCipher ( key , iv , aadLength : 0 ) , isAead : true ) ;
595+ break ;
596+ 597+ ivLength = 12 ;
598+ cipherInfo = new CipherInfo ( 256 , ( key , iv ) => new ChaCha20Poly1305Cipher ( key , aadLength : 0 ) , isAead : true ) ;
539599 break ;
540600 default :
541601 throw new SshException ( "Cipher '" + cipherName + "' is not supported for an OpenSSH key." ) ;
542602 }
543603
604+ var keyLength = cipherInfo . KeySize / 8 ;
605+
606+ // inspired by the SSHj library (https://github.com/hierynomus/sshj)
607+ // apply the kdf to derive a key and iv from the passphrase
608+ var passPhraseBytes = Encoding . UTF8 . GetBytes ( passPhrase ) ;
609+ var keyiv = new byte [ keyLength + ivLength ] ;
610+ new BCrypt ( ) . Pbkdf ( passPhraseBytes , salt , rounds , keyiv ) ;
611+
612+ var key = keyiv . Take ( keyLength ) ;
613+ var iv = keyiv . Take ( keyLength , ivLength ) ;
614+
615+ var cipher = cipherInfo . Cipher ( key , iv ) ;
616+ var cipherData = keyReader . ReadBytes ( privateKeyLength + cipher . TagSize ) ;
617+
544618 try
545619 {
546- privateKeyBytes = cipher . Decrypt ( privateKeyBytes ) ;
620+ privateKeyBytes = cipher . Decrypt ( cipherData , 0 , privateKeyLength ) ;
547621 }
548622 finally
549623 {
550- cipher . Dispose ( ) ;
624+ if ( cipher is IDisposable disposable )
625+ {
626+ disposable . Dispose ( ) ;
627+ }
551628 }
552629 }
630+ else
631+ {
632+ privateKeyBytes = keyReader . ReadBytes ( privateKeyLength ) ;
633+ }
553634
554635 // validate private key length
555636 privateKeyLength = privateKeyBytes . Length ;
0 commit comments