@@ -39,7 +39,7 @@ namespace Renci.SshNet
39
39
/// </list>
40
40
/// </para>
41
41
/// <para>
42
- /// The following encryption algorithms are supported:
42
+ /// The following encryption algorithms are supported for OpenSSL PEM and ssh.com format :
43
43
/// <list type="bullet">
44
44
/// <item>
45
45
/// <description>DES-EDE3-CBC</description>
@@ -60,6 +60,39 @@ namespace Renci.SshNet
60
60
/// <description>AES-256-CBC</description>
61
61
/// </item>
62
62
/// </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>
63
96
/// </para>
64
97
/// </remarks>
65
98
public partial class PrivateKeyFile : IPrivateKeySource , IDisposable
@@ -450,7 +483,17 @@ private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, strin
450
483
451
484
var cipher = cipherInfo . Cipher ( cipherKey . ToArray ( ) , binarySalt ) ;
452
485
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
+ }
454
497
}
455
498
456
499
/// <summary>
@@ -474,7 +517,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
474
517
throw new SshException ( "This openssh key does not contain the 'openssh-key-v1' format magic header" ) ;
475
518
}
476
519
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
478
521
var cipherName = keyReader . ReadString ( Encoding . UTF8 ) ;
479
522
480
523
// key derivation function (kdf): bcrypt or nothing
@@ -503,7 +546,7 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
503
546
504
547
// possibly encrypted private key
505
548
var privateKeyLength = ( int ) keyReader . ReadUInt32 ( ) ;
506
- var privateKeyBytes = keyReader . ReadBytes ( privateKeyLength ) ;
549
+ byte [ ] privateKeyBytes ;
507
550
508
551
// decrypt private key if necessary
509
552
if ( cipherName != "none" )
@@ -518,38 +561,76 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
518
561
throw new SshException ( "kdf " + kdfName + " is not supported for openssh key file" ) ;
519
562
}
520
563
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 ;
532
566
switch ( cipherName )
533
567
{
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 ;
534
578
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 ) ) ;
536
586
break ;
537
587
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 ) ;
539
599
break ;
540
600
default :
541
601
throw new SshException ( "Cipher '" + cipherName + "' is not supported for an OpenSSH key." ) ;
542
602
}
543
603
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
+
544
618
try
545
619
{
546
- privateKeyBytes = cipher . Decrypt ( privateKeyBytes ) ;
620
+ privateKeyBytes = cipher . Decrypt ( cipherData , 0 , privateKeyLength ) ;
547
621
}
548
622
finally
549
623
{
550
- cipher . Dispose ( ) ;
624
+ if ( cipher is IDisposable disposable )
625
+ {
626
+ disposable . Dispose ( ) ;
627
+ }
551
628
}
552
629
}
630
+ else
631
+ {
632
+ privateKeyBytes = keyReader . ReadBytes ( privateKeyLength ) ;
633
+ }
553
634
554
635
// validate private key length
555
636
privateKeyLength = privateKeyBytes . Length ;
0 commit comments