18
18
19
19
import java .io .ByteArrayOutputStream ;
20
20
import java .io .IOException ;
21
+ import java .security .AlgorithmParameters ;
21
22
import java .security .GeneralSecurityException ;
22
23
import java .security .KeyFactory ;
23
24
import java .security .PrivateKey ;
27
28
import java .util .Base64 ;
28
29
import java .util .Collections ;
29
30
import java .util .List ;
30
- import java .util .function .Function ;
31
+ import java .util .function .BiFunction ;
31
32
import java .util .regex .Matcher ;
32
33
import java .util .regex .Pattern ;
33
34
35
+ import javax .crypto .Cipher ;
36
+ import javax .crypto .EncryptedPrivateKeyInfo ;
37
+ import javax .crypto .SecretKey ;
38
+ import javax .crypto .SecretKeyFactory ;
39
+ import javax .crypto .spec .PBEKeySpec ;
40
+
41
+ import org .springframework .util .Assert ;
42
+
34
43
/**
35
44
* Parser for PKCS private key files in PEM format.
36
45
*
@@ -48,18 +57,27 @@ final class PemPrivateKeyParser {
48
57
49
58
private static final String PKCS8_FOOTER = "-+END\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
50
59
60
+ private static final String PKCS8_ENCRYPTED_HEADER = "-+BEGIN\\ s+ENCRYPTED\\ s+PRIVATE\\ s+KEY[^-]*-+(?:\\ s|\\ r|\\ n)+" ;
61
+
62
+ private static final String PKCS8_ENCRYPTED_FOOTER = "-+END\\ s+ENCRYPTED\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
63
+
51
64
private static final String EC_HEADER = "-+BEGIN\\ s+EC\\ s+PRIVATE\\ s+KEY[^-]*-+(?:\\ s|\\ r|\\ n)+" ;
52
65
53
66
private static final String EC_FOOTER = "-+END\\ s+EC\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
54
67
55
68
private static final String BASE64_TEXT = "([a-z0-9+/=\\ r\\ n]+)" ;
56
69
70
+ public static final int BASE64_TEXT_GROUP = 1 ;
71
+
57
72
private static final List <PemParser > PEM_PARSERS ;
58
73
static {
59
74
List <PemParser > parsers = new ArrayList <>();
60
75
parsers .add (new PemParser (PKCS1_HEADER , PKCS1_FOOTER , PemPrivateKeyParser ::createKeySpecForPkcs1 , "RSA" ));
61
76
parsers .add (new PemParser (EC_HEADER , EC_FOOTER , PemPrivateKeyParser ::createKeySpecForEc , "EC" ));
62
- parsers .add (new PemParser (PKCS8_HEADER , PKCS8_FOOTER , PKCS8EncodedKeySpec ::new , "RSA" , "EC" , "DSA" , "Ed25519" ));
77
+ parsers .add (new PemParser (PKCS8_HEADER , PKCS8_FOOTER , PemPrivateKeyParser ::createKeySpecForPkcs8 , "RSA" , "EC" ,
78
+ "DSA" , "Ed25519" ));
79
+ parsers .add (new PemParser (PKCS8_ENCRYPTED_HEADER , PKCS8_ENCRYPTED_FOOTER ,
80
+ PemPrivateKeyParser ::createKeySpecForPkcs8Encrypted , "RSA" , "EC" , "DSA" , "Ed25519" ));
63
81
PEM_PARSERS = Collections .unmodifiableList (parsers );
64
82
}
65
83
@@ -81,11 +99,11 @@ final class PemPrivateKeyParser {
81
99
private PemPrivateKeyParser () {
82
100
}
83
101
84
- private static PKCS8EncodedKeySpec createKeySpecForPkcs1 (byte [] bytes ) {
102
+ private static PKCS8EncodedKeySpec createKeySpecForPkcs1 (byte [] bytes , String password ) {
85
103
return createKeySpecForAlgorithm (bytes , RSA_ALGORITHM , null );
86
104
}
87
105
88
- private static PKCS8EncodedKeySpec createKeySpecForEc (byte [] bytes ) {
106
+ private static PKCS8EncodedKeySpec createKeySpecForEc (byte [] bytes , String password ) {
89
107
return createKeySpecForAlgorithm (bytes , EC_ALGORITHM , EC_PARAMETERS );
90
108
}
91
109
@@ -106,18 +124,37 @@ private static PKCS8EncodedKeySpec createKeySpecForAlgorithm(byte[] bytes, int[]
106
124
}
107
125
}
108
126
127
+ private static PKCS8EncodedKeySpec createKeySpecForPkcs8 (byte [] bytes , String password ) {
128
+ return new PKCS8EncodedKeySpec (bytes );
129
+ }
130
+
131
+ private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted (byte [] bytes , String password ) {
132
+ return Pkcs8PrivateKeyDecryptor .decrypt (bytes , password );
133
+ }
134
+
109
135
/**
110
136
* Parse a private key from the specified string.
111
137
* @param key the private key to parse
112
138
* @return the parsed private key
113
139
*/
114
140
static PrivateKey parse (String key ) {
141
+ return parse (key , null );
142
+ }
143
+
144
+ /**
145
+ * Parse a private key from the specified string, using the provided password for
146
+ * decryption if necessary.
147
+ * @param key the private key to parse
148
+ * @param password the password used to decrypt an encrypted private key
149
+ * @return the parsed private key
150
+ */
151
+ static PrivateKey parse (String key , String password ) {
115
152
if (key == null ) {
116
153
return null ;
117
154
}
118
155
try {
119
156
for (PemParser pemParser : PEM_PARSERS ) {
120
- PrivateKey privateKey = pemParser .parse (key );
157
+ PrivateKey privateKey = pemParser .parse (key , password );
121
158
if (privateKey != null ) {
122
159
return privateKey ;
123
160
}
@@ -136,30 +173,30 @@ private static class PemParser {
136
173
137
174
private final Pattern pattern ;
138
175
139
- private final Function <byte [], PKCS8EncodedKeySpec > keySpecFactory ;
176
+ private final BiFunction <byte [], String , PKCS8EncodedKeySpec > keySpecFactory ;
140
177
141
178
private final String [] algorithms ;
142
179
143
- PemParser (String header , String footer , Function <byte [], PKCS8EncodedKeySpec > keySpecFactory ,
180
+ PemParser (String header , String footer , BiFunction <byte [], String , PKCS8EncodedKeySpec > keySpecFactory ,
144
181
String ... algorithms ) {
145
182
this .pattern = Pattern .compile (header + BASE64_TEXT + footer , Pattern .CASE_INSENSITIVE );
146
- this .algorithms = algorithms ;
147
183
this .keySpecFactory = keySpecFactory ;
184
+ this .algorithms = algorithms ;
148
185
}
149
186
150
- PrivateKey parse (String text ) {
187
+ PrivateKey parse (String text , String password ) {
151
188
Matcher matcher = this .pattern .matcher (text );
152
- return (!matcher .find ()) ? null : parse (decodeBase64 (matcher .group (1 )) );
189
+ return (!matcher .find ()) ? null : parse (decodeBase64 (matcher .group (BASE64_TEXT_GROUP )), password );
153
190
}
154
191
155
192
private static byte [] decodeBase64 (String content ) {
156
193
byte [] contentBytes = content .replaceAll ("\r " , "" ).replaceAll ("\n " , "" ).getBytes ();
157
194
return Base64 .getDecoder ().decode (contentBytes );
158
195
}
159
196
160
- private PrivateKey parse (byte [] bytes ) {
197
+ private PrivateKey parse (byte [] bytes , String password ) {
161
198
try {
162
- PKCS8EncodedKeySpec keySpec = this .keySpecFactory .apply (bytes );
199
+ PKCS8EncodedKeySpec keySpec = this .keySpecFactory .apply (bytes , password );
163
200
for (String algorithm : this .algorithms ) {
164
201
KeyFactory keyFactory = KeyFactory .getInstance (algorithm );
165
202
try {
@@ -251,4 +288,34 @@ byte[] toByteArray() {
251
288
252
289
}
253
290
291
+ static class Pkcs8PrivateKeyDecryptor {
292
+
293
+ public static final String PBES2_ALGORITHM = "PBES2" ;
294
+
295
+ static PKCS8EncodedKeySpec decrypt (byte [] bytes , String password ) {
296
+ Assert .notNull (password , "Password is required for an encrypted private key" );
297
+ try {
298
+ EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo (bytes );
299
+ AlgorithmParameters algorithmParameters = keyInfo .getAlgParameters ();
300
+ String encryptionAlgorithm = getEncryptionAlgorithm (algorithmParameters , keyInfo .getAlgName ());
301
+ SecretKeyFactory keyFactory = SecretKeyFactory .getInstance (encryptionAlgorithm );
302
+ SecretKey key = keyFactory .generateSecret (new PBEKeySpec (password .toCharArray ()));
303
+ Cipher cipher = Cipher .getInstance (encryptionAlgorithm );
304
+ cipher .init (Cipher .DECRYPT_MODE , key , algorithmParameters );
305
+ return keyInfo .getKeySpec (cipher );
306
+ }
307
+ catch (IOException | GeneralSecurityException ex ) {
308
+ throw new IllegalArgumentException ("Error decrypting private key" , ex );
309
+ }
310
+ }
311
+
312
+ private static String getEncryptionAlgorithm (AlgorithmParameters algParameters , String algName ) {
313
+ if (algParameters != null && PBES2_ALGORITHM .equals (algName )) {
314
+ return algParameters .toString ();
315
+ }
316
+ return algName ;
317
+ }
318
+
319
+ }
320
+
254
321
}
0 commit comments