1 package org
.argeo
.cms
.ssh
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStreamReader
;
6 import java
.io
.StringReader
;
7 import java
.io
.StringWriter
;
8 import java
.nio
.charset
.StandardCharsets
;
9 import java
.nio
.file
.Files
;
10 import java
.nio
.file
.Path
;
11 import java
.nio
.file
.Paths
;
12 import java
.security
.GeneralSecurityException
;
13 import java
.security
.KeyFactory
;
14 import java
.security
.KeyPair
;
15 import java
.security
.PrivateKey
;
16 import java
.security
.PublicKey
;
17 import java
.security
.interfaces
.RSAPrivateCrtKey
;
18 import java
.security
.spec
.RSAPublicKeySpec
;
20 import org
.apache
.sshd
.common
.config
.keys
.KeyUtils
;
21 import org
.apache
.sshd
.common
.config
.keys
.PublicKeyEntry
;
22 import org
.bouncycastle
.asn1
.pkcs
.PrivateKeyInfo
;
23 import org
.bouncycastle
.openssl
.PEMDecryptorProvider
;
24 import org
.bouncycastle
.openssl
.PEMEncryptedKeyPair
;
25 import org
.bouncycastle
.openssl
.PEMKeyPair
;
26 import org
.bouncycastle
.openssl
.PEMParser
;
27 import org
.bouncycastle
.openssl
.PKCS8Generator
;
28 import org
.bouncycastle
.openssl
.bc
.BcPEMDecryptorProvider
;
29 import org
.bouncycastle
.openssl
.jcajce
.JcaPEMKeyConverter
;
30 import org
.bouncycastle
.openssl
.jcajce
.JcaPEMWriter
;
31 import org
.bouncycastle
.openssl
.jcajce
.JcaPKCS8Generator
;
32 import org
.bouncycastle
.openssl
.jcajce
.JceOpenSSLPKCS8DecryptorProviderBuilder
;
33 import org
.bouncycastle
.openssl
.jcajce
.JceOpenSSLPKCS8EncryptorBuilder
;
34 import org
.bouncycastle
.operator
.InputDecryptorProvider
;
35 import org
.bouncycastle
.operator
.OutputEncryptor
;
36 import org
.bouncycastle
.pkcs
.PKCS8EncryptedPrivateKeyInfo
;
38 @SuppressWarnings("restriction")
39 public class SshKeyPair
{
40 public final static String RSA_KEY_TYPE
= "ssh-rsa";
42 private PublicKey publicKey
;
43 private PrivateKey privateKey
;
44 private KeyPair keyPair
;
46 public SshKeyPair(KeyPair keyPair
) {
48 this.publicKey
= keyPair
.getPublic();
49 this.privateKey
= keyPair
.getPrivate();
50 this.keyPair
= keyPair
;
53 public SshKeyPair(PublicKey publicKey
, PrivateKey privateKey
) {
55 this.publicKey
= publicKey
;
56 this.privateKey
= privateKey
;
57 this.keyPair
= new KeyPair(publicKey
, privateKey
);
60 public KeyPair
asKeyPair() {
64 public String
getPublicKeyAsOpenSshString() {
65 return PublicKeyEntry
.toString(publicKey
);
68 public String
getPrivateKeyAsPemString(char[] password
) {
72 if (password
!= null) {
73 JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder
= new JceOpenSSLPKCS8EncryptorBuilder(
74 PKCS8Generator
.PBE_SHA1_3DES
);
75 encryptorBuilder
.setPasssword(password
);
76 OutputEncryptor oe
= encryptorBuilder
.build();
77 JcaPKCS8Generator gen
= new JcaPKCS8Generator(privateKey
, oe
);
83 StringWriter sw
= new StringWriter();
84 JcaPEMWriter pemWrt
= new JcaPEMWriter(sw
);
85 pemWrt
.writeObject(obj
);
88 } catch (Exception e
) {
89 throw new RuntimeException("Cannot convert private key", e
);
93 public static SshKeyPair
loadOrGenerate(Path privateKeyPath
, int size
, char[] password
) {
95 SshKeyPair sshKeyPair
;
96 if (Files
.exists(privateKeyPath
)) {
97 // String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII);
99 new InputStreamReader(Files
.newInputStream(privateKeyPath
), StandardCharsets
.US_ASCII
),
101 // TOD make sure public key is consistemt
103 sshKeyPair
= generate(size
);
104 Files
.write(privateKeyPath
,
105 sshKeyPair
.getPrivateKeyAsPemString(password
).getBytes(StandardCharsets
.US_ASCII
));
106 Path publicKeyPath
= privateKeyPath
.resolveSibling(privateKeyPath
.getFileName() + ".pub");
107 Files
.write(publicKeyPath
,
108 sshKeyPair
.getPublicKeyAsOpenSshString().getBytes(StandardCharsets
.US_ASCII
));
111 } catch (IOException e
) {
112 throw new RuntimeException("Cannot read or write private key " + privateKeyPath
, e
);
116 public static SshKeyPair
generate(int size
) {
117 return generate(RSA_KEY_TYPE
, size
);
120 public static SshKeyPair
generate(String keyType
, int size
) {
122 KeyPair keyPair
= KeyUtils
.generateKeyPair(keyType
, size
);
123 PublicKey publicKey
= keyPair
.getPublic();
124 PrivateKey privateKey
= keyPair
.getPrivate();
125 return new SshKeyPair(publicKey
, privateKey
);
126 } catch (GeneralSecurityException e
) {
127 throw new RuntimeException("Cannot generate SSH key", e
);
131 public static SshKeyPair
loadDefault(char[] password
) {
132 Path privateKeyPath
= Paths
.get(System
.getProperty("user.home") + "/.ssh/id_rsa");
133 // TODO try other formats
134 return load(privateKeyPath
, password
);
137 public static SshKeyPair
load(Path privateKeyPath
, char[] password
) {
138 try (Reader reader
= Files
.newBufferedReader(privateKeyPath
)) {
139 return load(reader
, password
);
140 } catch (IOException e
) {
141 throw new IllegalStateException("Cannot load private key from " + privateKeyPath
, e
);
146 public static SshKeyPair
load(Reader reader
, char[] password
) {
147 try (PEMParser pemParser
= new PEMParser(reader
)) {
148 Object object
= pemParser
.readObject();
149 JcaPEMKeyConverter converter
= new JcaPEMKeyConverter();// .setProvider("BC");
151 if (object
instanceof PEMEncryptedKeyPair
) {
152 PEMEncryptedKeyPair ekp
= (PEMEncryptedKeyPair
) object
;
153 PEMDecryptorProvider decryptorProvider
= new BcPEMDecryptorProvider(password
);
154 PEMKeyPair pemKp
= ekp
.decryptKeyPair(decryptorProvider
);
155 kp
= converter
.getKeyPair(pemKp
);
156 } else if (object
instanceof PKCS8EncryptedPrivateKeyInfo
) {
157 // Encrypted key - we will use provided password
158 PKCS8EncryptedPrivateKeyInfo ckp
= (PKCS8EncryptedPrivateKeyInfo
) object
;
159 // PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password);
160 InputDecryptorProvider inputDecryptorProvider
= new JceOpenSSLPKCS8DecryptorProviderBuilder()
162 PrivateKeyInfo pkInfo
= ckp
.decryptPrivateKeyInfo(inputDecryptorProvider
);
163 PrivateKey privateKey
= converter
.getPrivateKey(pkInfo
);
165 // generate public key
166 RSAPrivateCrtKey privk
= (RSAPrivateCrtKey
) privateKey
;
167 RSAPublicKeySpec publicKeySpec
= new java
.security
.spec
.RSAPublicKeySpec(privk
.getModulus(),
168 privk
.getPublicExponent());
169 KeyFactory keyFactory
= KeyFactory
.getInstance("RSA");
170 PublicKey publicKey
= keyFactory
.generatePublic(publicKeySpec
);
172 kp
= new KeyPair(publicKey
, privateKey
);
174 // Unencrypted key - no password needed
175 // PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object;
176 PEMKeyPair pemKp
= (PEMKeyPair
) object
;
177 kp
= converter
.getKeyPair(pemKp
);
179 return new SshKeyPair(kp
);
180 } catch (Exception e
) {
181 throw new RuntimeException("Cannot load private key", e
);
185 public static void main(String args
[]) {
186 Path privateKeyPath
= Paths
.get(System
.getProperty("user.dir") + "/id_rsa");
187 SshKeyPair skp
= SshKeyPair
.loadOrGenerate(privateKeyPath
, 1024, null);
188 System
.out
.println("Public:\n" + skp
.getPublicKeyAsOpenSshString());
189 System
.out
.println("Private (plain):\n" + skp
.getPrivateKeyAsPemString(null));
190 System
.out
.println("Private (encrypted):\n" + skp
.getPrivateKeyAsPemString("demo".toCharArray()));
192 StringReader reader
= new StringReader(skp
.getPrivateKeyAsPemString(null));
193 skp
= SshKeyPair
.load(reader
, null);
194 System
.out
.println("Public:\n" + skp
.getPublicKeyAsOpenSshString());
195 System
.out
.println("Private (plain):\n" + skp
.getPrivateKeyAsPemString(null));
196 System
.out
.println("Private (encrypted):\n" + skp
.getPrivateKeyAsPemString("demo".toCharArray()));
198 reader
= new StringReader(skp
.getPrivateKeyAsPemString("demo".toCharArray()));
199 skp
= SshKeyPair
.load(reader
, "demo".toCharArray());
200 System
.out
.println("Public:\n" + skp
.getPublicKeyAsOpenSshString());
201 System
.out
.println("Private (plain):\n" + skp
.getPrivateKeyAsPemString(null));
202 System
.out
.println("Private (encrypted):\n" + skp
.getPrivateKeyAsPemString("demo".toCharArray()));