1515 */
1616package com .hierynomus .sshj .userauth .keyprovider ;
1717
18+ import com .hierynomus .sshj .transport .cipher .BlockCiphers ;
1819import net .i2p .crypto .eddsa .EdDSAPrivateKey ;
1920import net .i2p .crypto .eddsa .spec .EdDSANamedCurveTable ;
2021import net .i2p .crypto .eddsa .spec .EdDSAPrivateKeySpec ;
2122import net .schmizz .sshj .common .*;
2223import net .schmizz .sshj .common .Buffer .PlainBuffer ;
24+ import net .schmizz .sshj .transport .cipher .Cipher ;
2325import net .schmizz .sshj .userauth .keyprovider .BaseFileKeyProvider ;
2426import net .schmizz .sshj .userauth .keyprovider .FileKeyProvider ;
2527import net .schmizz .sshj .userauth .keyprovider .KeyFormat ;
28+ import org .mindrot .jbcrypt .BCrypt ;
2629import org .slf4j .Logger ;
2730import org .slf4j .LoggerFactory ;
2831
2932import java .io .BufferedReader ;
3033import java .io .IOException ;
34+ import java .nio .ByteBuffer ;
35+ import java .nio .CharBuffer ;
36+ import java .nio .charset .Charset ;
3137import java .security .GeneralSecurityException ;
3238import java .security .KeyPair ;
3339import java .security .PublicKey ;
40+ import java .util .Arrays ;
3441
3542/**
3643 * Reads a key file in the new OpenSSH format.
@@ -42,6 +49,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
4249 private static final String END = "-----END " ;
4350 private static final byte [] AUTH_MAGIC = "openssh-key-v1\0 " .getBytes ();
4451 public static final String OPENSSH_PRIVATE_KEY = "OPENSSH PRIVATE KEY-----" ;
52+ public static final String BCRYPT = "bcrypt" ;
4553
4654 public static class Factory
4755 implements net .schmizz .sshj .common .Factory .Named <FileKeyProvider > {
@@ -86,7 +94,7 @@ private KeyPair readDecodedKeyPair(final PlainBuffer keyBuffer) throws IOExcepti
8694
8795 String cipherName = keyBuffer .readString (); // string ciphername
8896 String kdfName = keyBuffer .readString (); // string kdfname
89- String kdfOptions = keyBuffer .readString (); // string kdfoptions
97+ byte [] kdfOptions = keyBuffer .readBytes (); // string kdfoptions
9098
9199 int nrKeys = keyBuffer .readUInt32AsInt (); // int number of keys N; Should be 1
92100 if (nrKeys != 1 ) {
@@ -99,10 +107,44 @@ private KeyPair readDecodedKeyPair(final PlainBuffer keyBuffer) throws IOExcepti
99107 return readUnencrypted (privateKeyBuffer , publicKey );
100108 } else {
101109 logger .info ("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + kdfOptions );
102- throw new IOException ("Cannot read encrypted keypair with " + cipherName + " yet." );
110+ PlainBuffer decrypted = decryptBuffer (privateKeyBuffer , cipherName , kdfName , kdfOptions );
111+ return readUnencrypted (decrypted , publicKey );
112+ // throw new IOException("Cannot read encrypted keypair with " + cipherName + " yet.");
103113 }
104114 }
105115
116+ private PlainBuffer decryptBuffer (PlainBuffer privateKeyBuffer , String cipherName , String kdfName , byte [] kdfOptions ) throws IOException {
117+ Cipher cipher = createCipher (cipherName );
118+ initializeCipher (kdfName , kdfOptions , cipher );
119+ byte [] array = privateKeyBuffer .array ();
120+ cipher .update (array , 0 , privateKeyBuffer .available ());
121+ return new PlainBuffer (array );
122+ }
123+
124+ private void initializeCipher (String kdfName , byte [] kdfOptions , Cipher cipher ) throws Buffer .BufferException {
125+ if (kdfName .equals (BCRYPT )) {
126+ PlainBuffer opts = new PlainBuffer (kdfOptions );
127+ CharBuffer charBuffer = CharBuffer .wrap (pwdf .reqPassword (null ));
128+ ByteBuffer byteBuffer = Charset .forName ("UTF-8" ).encode (charBuffer );
129+ byte [] passphrase = Arrays .copyOfRange (byteBuffer .array (),
130+ byteBuffer .position (), byteBuffer .limit ());
131+ byte [] keyiv = new byte [48 ];
132+ new BCrypt ().pbkdf (passphrase , opts .readBytes (), opts .readUInt32AsInt (), keyiv );
133+ byte [] key = Arrays .copyOfRange (keyiv , 0 , 32 );
134+ byte [] iv = Arrays .copyOfRange (keyiv , 32 , 48 );
135+ cipher .init (Cipher .Mode .Decrypt , key , iv );
136+ } else {
137+ throw new IllegalStateException ("No support for KDF '" + kdfName + "'." );
138+ }
139+ }
140+
141+ private Cipher createCipher (String cipherName ) {
142+ if (cipherName .equals (BlockCiphers .AES256CTR ().getName ())) {
143+ return BlockCiphers .AES256CTR ().create ();
144+ }
145+ throw new IllegalStateException ("Cipher '" + cipherName + "' not currently implemented for openssh-key-v1 format" );
146+ }
147+
106148 private PublicKey readPublicKey (final PlainBuffer plainBuffer ) throws Buffer .BufferException , GeneralSecurityException {
107149 return KeyType .fromString (plainBuffer .readString ()).readPubKeyFromBuffer (plainBuffer );
108150 }
0 commit comments