001/* 002 * $HeadURL: file:///opt/dev/not-yet-commons-ssl-SVN-repo/tags/commons-ssl-0.3.17/src/java/org/apache/commons/ssl/PKCS8Key.java $ 003 * $Revision: 183 $ 004 * $Date: 2015-03-16 12:12:27 -0700 (Mon, 16 Mar 2015) $ 005 * 006 * ==================================================================== 007 * Licensed to the Apache Software Foundation (ASF) under one 008 * or more contributor license agreements. See the NOTICE file 009 * distributed with this work for additional information 010 * regarding copyright ownership. The ASF licenses this file 011 * to you under the Apache License, Version 2.0 (the 012 * "License"); you may not use this file except in compliance 013 * with the License. You may obtain a copy of the License at 014 * 015 * http://www.apache.org/licenses/LICENSE-2.0 016 * 017 * Unless required by applicable law or agreed to in writing, 018 * software distributed under the License is distributed on an 019 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 020 * KIND, either express or implied. See the License for the 021 * specific language governing permissions and limitations 022 * under the License. 023 * ==================================================================== 024 * 025 * This software consists of voluntary contributions made by many 026 * individuals on behalf of the Apache Software Foundation. For more 027 * information on the Apache Software Foundation, please see 028 * <http://www.apache.org/>. 029 * 030 */ 031 032package org.apache.commons.ssl; 033 034import java.io.ByteArrayInputStream; 035import java.io.ByteArrayOutputStream; 036import java.io.File; 037import java.io.FileInputStream; 038import java.io.IOException; 039import java.io.InputStream; 040import java.math.BigInteger; 041import java.security.GeneralSecurityException; 042import java.security.InvalidAlgorithmParameterException; 043import java.security.InvalidKeyException; 044import java.security.KeyFactory; 045import java.security.MessageDigest; 046import java.security.NoSuchAlgorithmException; 047import java.security.PrivateKey; 048import java.security.PublicKey; 049import java.security.interfaces.DSAParams; 050import java.security.interfaces.DSAPrivateKey; 051import java.security.interfaces.RSAPrivateCrtKey; 052import java.security.spec.DSAPublicKeySpec; 053import java.security.spec.KeySpec; 054import java.security.spec.PKCS8EncodedKeySpec; 055import java.security.spec.RSAPublicKeySpec; 056import java.util.Arrays; 057import java.util.Collections; 058import java.util.Iterator; 059import java.util.List; 060 061import javax.crypto.BadPaddingException; 062import javax.crypto.Cipher; 063import javax.crypto.IllegalBlockSizeException; 064import javax.crypto.Mac; 065import javax.crypto.NoSuchPaddingException; 066import javax.crypto.SecretKey; 067import javax.crypto.spec.IvParameterSpec; 068import javax.crypto.spec.RC2ParameterSpec; 069import javax.crypto.spec.RC5ParameterSpec; 070import javax.crypto.spec.SecretKeySpec; 071 072import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1Encodable; 073import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1EncodableVector; 074import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1Integer; 075import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1ObjectIdentifier; 076import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1OutputStream; 077import org.apache.commons.ssl.org.bouncycastle.asn1.DERNull; 078import org.apache.commons.ssl.org.bouncycastle.asn1.DEROctetString; 079import org.apache.commons.ssl.org.bouncycastle.asn1.DERSequence; 080 081/** 082 * Utility for decrypting PKCS8 private keys. Way easier to use than 083 * javax.crypto.EncryptedPrivateKeyInfo since all you need is the byte[] array 084 * and the password. You don't need to know anything else about the PKCS8 085 * key you pass in. 086 * </p><p> 087 * Can handle base64 PEM, or raw DER. 088 * Can handle PKCS8 Version 1.5 and 2.0. 089 * Can also handle OpenSSL encrypted or unencrypted private keys (DSA or RSA). 090 * </p><p> 091 * The PKCS12 key derivation (the "pkcs12()" method) comes from BouncyCastle. 092 * </p> 093 * 094 * @author Credit Union Central of British Columbia 095 * @author <a href="http://www.cucbc.com/">www.cucbc.com</a> 096 * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a> 097 * @author <a href="bouncycastle.org">bouncycastle.org</a> 098 * @since 7-Nov-2006 099 */ 100public class PKCS8Key { 101 public final static String RSA_OID = "1.2.840.113549.1.1.1"; 102 public final static String DSA_OID = "1.2.840.10040.4.1"; 103 104 public final static String PKCS8_UNENCRYPTED = "PRIVATE KEY"; 105 public final static String PKCS8_ENCRYPTED = "ENCRYPTED PRIVATE KEY"; 106 public final static String OPENSSL_RSA = "RSA PRIVATE KEY"; 107 public final static String OPENSSL_DSA = "DSA PRIVATE KEY"; 108 109 private final PrivateKey privateKey; 110 private final byte[] decryptedBytes; 111 private final String transformation; 112 private final int keySize; 113 private final boolean isDSA; 114 private final boolean isRSA; 115 116 static { 117 JavaImpl.load(); 118 } 119 120 /** 121 * @param in pkcs8 file to parse (pem or der, encrypted or unencrypted) 122 * @param password password to decrypt the pkcs8 file. Ignored if the 123 * supplied pkcs8 is already unencrypted. 124 * @throws GeneralSecurityException If a parsing or decryption problem 125 * occured. 126 * @throws IOException If the supplied InputStream could not be read. 127 */ 128 public PKCS8Key(final InputStream in, char[] password) 129 throws GeneralSecurityException, IOException { 130 this(Util.streamToBytes(in), password); 131 } 132 133 /** 134 * @param in pkcs8 file to parse (pem or der, encrypted or unencrypted) 135 * @param password password to decrypt the pkcs8 file. Ignored if the 136 * supplied pkcs8 is already unencrypted. 137 * @throws GeneralSecurityException If a parsing or decryption problem 138 * occured. 139 */ 140 public PKCS8Key(final ByteArrayInputStream in, char[] password) 141 throws GeneralSecurityException { 142 this(Util.streamToBytes(in), password); 143 } 144 145 /** 146 * @param encoded pkcs8 file to parse (pem or der, encrypted or unencrypted) 147 * @param password password to decrypt the pkcs8 file. Ignored if the 148 * supplied pkcs8 is already unencrypted. 149 * @throws GeneralSecurityException If a parsing or decryption problem 150 * occured. 151 */ 152 public PKCS8Key(final byte[] encoded, char[] password) 153 throws GeneralSecurityException { 154 DecryptResult decryptResult = 155 new DecryptResult("UNENCRYPTED", 0, encoded); 156 157 List pemItems = PEMUtil.decode(encoded); 158 PEMItem keyItem = null; 159 byte[] derBytes = null; 160 if (pemItems.isEmpty()) { 161 // must be DER encoded - PEMUtil wasn't able to extract anything. 162 derBytes = encoded; 163 } else { 164 Iterator it = pemItems.iterator(); 165 boolean opensslRSA = false; 166 boolean opensslDSA = false; 167 168 while (it.hasNext()) { 169 PEMItem item = (PEMItem) it.next(); 170 String type = item.pemType.trim().toUpperCase(); 171 boolean plainPKCS8 = type.startsWith(PKCS8_UNENCRYPTED); 172 boolean encryptedPKCS8 = type.startsWith(PKCS8_ENCRYPTED); 173 boolean rsa = type.startsWith(OPENSSL_RSA); 174 boolean dsa = type.startsWith(OPENSSL_DSA); 175 if (plainPKCS8 || encryptedPKCS8 || rsa || dsa) { 176 opensslRSA = opensslRSA || rsa; 177 opensslDSA = opensslDSA || dsa; 178 if (derBytes != null) { 179 throw new ProbablyNotPKCS8Exception("More than one pkcs8 or OpenSSL key found in the supplied PEM Base64 stream"); 180 } 181 derBytes = item.getDerBytes(); 182 keyItem = item; 183 decryptResult = new DecryptResult("UNENCRYPTED", 0, derBytes); 184 } 185 } 186 // after the loop is finished, did we find anything? 187 if (derBytes == null) { 188 throw new ProbablyNotPKCS8Exception("No pkcs8 or OpenSSL key found in the supplied PEM Base64 stream"); 189 } 190 191 if (opensslDSA || opensslRSA) { 192 String c = keyItem.cipher.trim(); 193 boolean encrypted = !"UNKNOWN".equals(c) && !"".equals(c); 194 if (encrypted) { 195 decryptResult = opensslDecrypt(keyItem, password); 196 } 197 198 String oid = RSA_OID; 199 if (opensslDSA) { 200 oid = DSA_OID; 201 } 202 derBytes = formatAsPKCS8(decryptResult.bytes, oid, null); 203 204 String tf = decryptResult.transformation; 205 int ks = decryptResult.keySize; 206 decryptResult = new DecryptResult(tf, ks, derBytes); 207 } 208 } 209 210 ASN1Structure pkcs8; 211 try { 212 pkcs8 = ASN1Util.analyze(derBytes); 213 } 214 catch (Exception e) { 215 throw new ProbablyNotPKCS8Exception("asn1 parse failure: " + e); 216 } 217 218 String oid = RSA_OID; 219 // With the OpenSSL unencrypted private keys in DER format, the only way 220 // to even have a hope of guessing what we've got (DSA or RSA?) is to 221 // count the number of DERIntegers occurring in the first DERSequence. 222 int derIntegerCount = -1; 223 if (pkcs8.derIntegers != null) { 224 derIntegerCount = pkcs8.derIntegers.size(); 225 } 226 switch (derIntegerCount) { 227 case 6: 228 oid = DSA_OID; 229 case 9: 230 derBytes = formatAsPKCS8(derBytes, oid, pkcs8); 231 pkcs8.oid1 = oid; 232 233 String tf = decryptResult.transformation; 234 int ks = decryptResult.keySize; 235 decryptResult = new DecryptResult(tf, ks, derBytes); 236 break; 237 default: 238 break; 239 } 240 241 oid = pkcs8.oid1; 242 if (!oid.startsWith("1.2.840.113549.1")) { 243 boolean isOkay = false; 244 if (oid.startsWith("1.2.840.10040.4.")) { 245 String s = oid.substring("1.2.840.10040.4.".length()); 246 // 1.2.840.10040.4.1 -- id-dsa 247 // 1.2.840.10040.4.3 -- id-dsa-with-sha1 248 isOkay = s.equals("1") || s.startsWith("1.") || 249 s.equals("3") || s.startsWith("3."); 250 } 251 if (!isOkay) { 252 throw new ProbablyNotPKCS8Exception("Valid ASN.1, but not PKCS8 or OpenSSL format. OID=" + oid); 253 } 254 } 255 256 boolean isRSA = RSA_OID.equals(oid); 257 boolean isDSA = DSA_OID.equals(oid); 258 boolean encrypted = !isRSA && !isDSA; 259 byte[] decryptedPKCS8 = encrypted ? null : derBytes; 260 261 if (encrypted) { 262 decryptResult = decryptPKCS8(pkcs8, password); 263 decryptedPKCS8 = decryptResult.bytes; 264 } 265 if (encrypted) { 266 try { 267 pkcs8 = ASN1Util.analyze(decryptedPKCS8); 268 } 269 catch (Exception e) { 270 throw new ProbablyBadPasswordException("Decrypted stream not ASN.1. Probably bad decryption password."); 271 } 272 oid = pkcs8.oid1; 273 isDSA = DSA_OID.equals(oid); 274 } 275 276 KeySpec spec = new PKCS8EncodedKeySpec(decryptedPKCS8); 277 String type = "RSA"; 278 PrivateKey pk; 279 try { 280 KeyFactory KF; 281 if (isDSA) { 282 type = "DSA"; 283 KF = KeyFactory.getInstance("DSA"); 284 } else { 285 KF = KeyFactory.getInstance("RSA"); 286 } 287 pk = KF.generatePrivate(spec); 288 } 289 catch (Exception e) { 290 throw new ProbablyBadPasswordException("Cannot create " + type + " private key from decrypted stream. Probably bad decryption password. " + e); 291 } 292 if (pk != null) { 293 this.privateKey = pk; 294 this.isDSA = isDSA; 295 this.isRSA = !isDSA; 296 this.decryptedBytes = decryptedPKCS8; 297 this.transformation = decryptResult.transformation; 298 this.keySize = decryptResult.keySize; 299 } else { 300 throw new GeneralSecurityException("KeyFactory.generatePrivate() returned null and didn't throw exception!"); 301 } 302 } 303 304 public boolean isRSA() { 305 return isRSA; 306 } 307 308 public boolean isDSA() { 309 return isDSA; 310 } 311 312 public String getTransformation() { 313 return transformation; 314 } 315 316 public int getKeySize() { 317 return keySize; 318 } 319 320 public byte[] getDecryptedBytes() { 321 return decryptedBytes; 322 } 323 324 public PrivateKey getPrivateKey() { 325 return privateKey; 326 } 327 328 public PublicKey getPublicKey() throws GeneralSecurityException { 329 if (privateKey instanceof DSAPrivateKey) { 330 DSAPrivateKey dsa = (DSAPrivateKey) privateKey; 331 DSAParams params = dsa.getParams(); 332 BigInteger g = params.getG(); 333 BigInteger p = params.getP(); 334 BigInteger q = params.getQ(); 335 BigInteger x = dsa.getX(); 336 BigInteger y = q.modPow( x, p ); 337 DSAPublicKeySpec dsaKeySpec = new DSAPublicKeySpec(y, p, q, g); 338 return KeyFactory.getInstance("DSA").generatePublic(dsaKeySpec); 339 } else if (privateKey instanceof RSAPrivateCrtKey) { 340 RSAPrivateCrtKey rsa = (RSAPrivateCrtKey) privateKey; 341 RSAPublicKeySpec rsaKeySpec = new RSAPublicKeySpec( 342 rsa.getModulus(), 343 rsa.getPublicExponent() 344 ); 345 return KeyFactory.getInstance("RSA").generatePublic(rsaKeySpec); 346 } else { 347 throw new GeneralSecurityException("Not an RSA or DSA key"); 348 } 349 } 350 351 public static class DecryptResult { 352 public final String transformation; 353 public final int keySize; 354 public final byte[] bytes; 355 356 protected DecryptResult(String transformation, int keySize, 357 byte[] decryptedBytes) { 358 this.transformation = transformation; 359 this.keySize = keySize; 360 this.bytes = decryptedBytes; 361 } 362 } 363 364 private static DecryptResult opensslDecrypt(final PEMItem item, 365 final char[] password) 366 throws GeneralSecurityException { 367 final String cipher = item.cipher; 368 final String mode = item.mode; 369 final int keySize = item.keySizeInBits; 370 final byte[] salt = item.iv; 371 final boolean des2 = item.des2; 372 final DerivedKey dk = OpenSSL.deriveKey(password, salt, keySize, des2); 373 return decrypt(cipher, mode, dk, des2, null, item.getDerBytes()); 374 } 375 376 public static Cipher generateCipher(String cipher, String mode, 377 final DerivedKey dk, 378 final boolean des2, 379 final byte[] iv, 380 final boolean decryptMode) 381 throws NoSuchAlgorithmException, NoSuchPaddingException, 382 InvalidKeyException, InvalidAlgorithmParameterException { 383 if (des2 && dk.key.length >= 24) { 384 // copy first 8 bytes into last 8 bytes to create 2DES key. 385 System.arraycopy(dk.key, 0, dk.key, 16, 8); 386 } 387 388 final int keySize = dk.key.length * 8; 389 cipher = cipher.trim(); 390 String cipherUpper = cipher.toUpperCase(); 391 mode = mode.trim().toUpperCase(); 392 // Is the cipher even available? 393 Cipher.getInstance(cipher); 394 String padding = "PKCS5Padding"; 395 if (mode.startsWith("CFB") || mode.startsWith("OFB")) { 396 padding = "NoPadding"; 397 } 398 399 String transformation = cipher + "/" + mode + "/" + padding; 400 if (cipherUpper.startsWith("RC4")) { 401 // RC4 does not take mode or padding. 402 transformation = cipher; 403 } 404 405 SecretKey secret = new SecretKeySpec(dk.key, cipher); 406 IvParameterSpec ivParams; 407 if (iv != null) { 408 ivParams = new IvParameterSpec(iv); 409 } else { 410 ivParams = dk.iv != null ? new IvParameterSpec(dk.iv) : null; 411 } 412 413 Cipher c = Cipher.getInstance(transformation); 414 int cipherMode = Cipher.ENCRYPT_MODE; 415 if (decryptMode) { 416 cipherMode = Cipher.DECRYPT_MODE; 417 } 418 419 // RC2 requires special params to inform engine of keysize. 420 if (cipherUpper.startsWith("RC2")) { 421 RC2ParameterSpec rcParams; 422 if (mode.startsWith("ECB") || ivParams == null) { 423 // ECB doesn't take an IV. 424 rcParams = new RC2ParameterSpec(keySize); 425 } else { 426 rcParams = new RC2ParameterSpec(keySize, ivParams.getIV()); 427 } 428 c.init(cipherMode, secret, rcParams); 429 } else if (cipherUpper.startsWith("RC5")) { 430 RC5ParameterSpec rcParams; 431 if (mode.startsWith("ECB") || ivParams == null) { 432 // ECB doesn't take an IV. 433 rcParams = new RC5ParameterSpec(16, 12, 32); 434 } else { 435 rcParams = new RC5ParameterSpec(16, 12, 32, ivParams.getIV()); 436 } 437 c.init(cipherMode, secret, rcParams); 438 } else if (mode.startsWith("ECB") || cipherUpper.startsWith("RC4")) { 439 // RC4 doesn't require any params. 440 // Any cipher using ECB does not require an IV. 441 c.init(cipherMode, secret); 442 } else { 443 // DES, DESede, AES, BlowFish require IVParams (when in CBC, CFB, 444 // or OFB mode). (In ECB mode they don't require IVParams). 445 c.init(cipherMode, secret, ivParams); 446 } 447 return c; 448 } 449 450 public static DecryptResult decrypt(String cipher, String mode, 451 final DerivedKey dk, 452 final boolean des2, 453 final byte[] iv, 454 final byte[] encryptedBytes) 455 456 throws NoSuchAlgorithmException, NoSuchPaddingException, 457 InvalidKeyException, InvalidAlgorithmParameterException, 458 IllegalBlockSizeException, BadPaddingException { 459 Cipher c = generateCipher(cipher, mode, dk, des2, iv, true); 460 final String transformation = c.getAlgorithm(); 461 final int keySize = dk.key.length * 8; 462 byte[] decryptedBytes = c.doFinal(encryptedBytes); 463 return new DecryptResult(transformation, keySize, decryptedBytes); 464 } 465 466 private static DecryptResult decryptPKCS8(ASN1Structure pkcs8, 467 char[] password) 468 throws GeneralSecurityException { 469 boolean isVersion1 = true; 470 boolean isVersion2 = false; 471 boolean usePKCS12PasswordPadding = false; 472 boolean use2DES = false; 473 String cipher = null; 474 String hash = null; 475 int keySize = -1; 476 // Almost all PKCS8 encrypted keys use CBC. Looks like the AES OID's can 477 // support different modes, and RC4 doesn't use any mode at all! 478 String mode = "CBC"; 479 480 // In PKCS8 Version 2 the IV is stored in the ASN.1 structure for 481 // us, so we don't need to derive it. Just leave "ivSize" set to 0 for 482 // those ones. 483 int ivSize = 0; 484 485 String oid = pkcs8.oid1; 486 if (oid.startsWith("1.2.840.113549.1.12.")) // PKCS12 key derivation! 487 { 488 usePKCS12PasswordPadding = true; 489 490 // Let's trim this OID to make life a little easier. 491 oid = oid.substring("1.2.840.113549.1.12.".length()); 492 493 if (oid.equals("1.1") || oid.startsWith("1.1.")) { 494 // 1.2.840.113549.1.12.1.1 495 hash = "SHA1"; 496 cipher = "RC4"; 497 keySize = 128; 498 } else if (oid.equals("1.2") || oid.startsWith("1.2.")) { 499 // 1.2.840.113549.1.12.1.2 500 hash = "SHA1"; 501 cipher = "RC4"; 502 keySize = 40; 503 } else if (oid.equals("1.3") || oid.startsWith("1.3.")) { 504 // 1.2.840.113549.1.12.1.3 505 hash = "SHA1"; 506 cipher = "DESede"; 507 keySize = 192; 508 } else if (oid.equals("1.4") || oid.startsWith("1.4.")) { 509 // DES2 !!! 510 511 // 1.2.840.113549.1.12.1.4 512 hash = "SHA1"; 513 cipher = "DESede"; 514 keySize = 192; 515 use2DES = true; 516 // later on we'll copy the first 8 bytes of the 24 byte DESede key 517 // over top the last 8 bytes, making the key look like K1-K2-K1 518 // instead of the usual K1-K2-K3. 519 } else if (oid.equals("1.5") || oid.startsWith("1.5.")) { 520 // 1.2.840.113549.1.12.1.5 521 hash = "SHA1"; 522 cipher = "RC2"; 523 keySize = 128; 524 } else if (oid.equals("1.6") || oid.startsWith("1.6.")) { 525 // 1.2.840.113549.1.12.1.6 526 hash = "SHA1"; 527 cipher = "RC2"; 528 keySize = 40; 529 } 530 } else if (oid.startsWith("1.2.840.113549.1.5.")) { 531 // Let's trim this OID to make life a little easier. 532 oid = oid.substring("1.2.840.113549.1.5.".length()); 533 534 if (oid.equals("1") || oid.startsWith("1.")) { 535 // 1.2.840.113549.1.5.1 -- pbeWithMD2AndDES-CBC 536 hash = "MD2"; 537 cipher = "DES"; 538 keySize = 64; 539 } else if (oid.equals("3") || oid.startsWith("3.")) { 540 // 1.2.840.113549.1.5.3 -- pbeWithMD5AndDES-CBC 541 hash = "MD5"; 542 cipher = "DES"; 543 keySize = 64; 544 } else if (oid.equals("4") || oid.startsWith("4.")) { 545 // 1.2.840.113549.1.5.4 -- pbeWithMD2AndRC2_CBC 546 hash = "MD2"; 547 cipher = "RC2"; 548 keySize = 64; 549 } else if (oid.equals("6") || oid.startsWith("6.")) { 550 // 1.2.840.113549.1.5.6 -- pbeWithMD5AndRC2_CBC 551 hash = "MD5"; 552 cipher = "RC2"; 553 keySize = 64; 554 } else if (oid.equals("10") || oid.startsWith("10.")) { 555 // 1.2.840.113549.1.5.10 -- pbeWithSHA1AndDES-CBC 556 hash = "SHA1"; 557 cipher = "DES"; 558 keySize = 64; 559 } else if (oid.equals("11") || oid.startsWith("11.")) { 560 // 1.2.840.113549.1.5.11 -- pbeWithSHA1AndRC2_CBC 561 hash = "SHA1"; 562 cipher = "RC2"; 563 keySize = 64; 564 } else if (oid.equals("12") || oid.startsWith("12.")) { 565 // 1.2.840.113549.1.5.12 - id-PBKDF2 - Key Derivation Function 566 isVersion2 = true; 567 } else if (oid.equals("13") || oid.startsWith("13.")) { 568 // 1.2.840.113549.1.5.13 - id-PBES2: PBES2 encryption scheme 569 isVersion2 = true; 570 } else if (oid.equals("14") || oid.startsWith("14.")) { 571 // 1.2.840.113549.1.5.14 - id-PBMAC1 message authentication scheme 572 isVersion2 = true; 573 } 574 } 575 if (isVersion2) { 576 isVersion1 = false; 577 hash = "HmacSHA1"; 578 oid = pkcs8.oid2; 579 580 // really ought to be: 581 // 582 // if ( oid.startsWith( "1.2.840.113549.1.5.12" ) ) 583 // 584 // but all my tests still pass, and I figure this to be more robust: 585 if (pkcs8.oid3 != null) { 586 oid = pkcs8.oid3; 587 } 588 if (oid.startsWith("1.3.6.1.4.1.3029.1.2")) { 589 // 1.3.6.1.4.1.3029.1.2 - Blowfish 590 cipher = "Blowfish"; 591 mode = "CBC"; 592 keySize = 128; 593 } else if (oid.startsWith("1.3.14.3.2.")) { 594 oid = oid.substring("1.3.14.3.2.".length()); 595 if (oid.equals("6") || oid.startsWith("6.")) { 596 // 1.3.14.3.2.6 - desECB 597 cipher = "DES"; 598 mode = "ECB"; 599 keySize = 64; 600 } else if (oid.equals("7") || oid.startsWith("7.")) { 601 // 1.3.14.3.2.7 - desCBC 602 cipher = "DES"; 603 mode = "CBC"; 604 keySize = 64; 605 } else if (oid.equals("8") || oid.startsWith("8.")) { 606 // 1.3.14.3.2.8 - desOFB 607 cipher = "DES"; 608 mode = "OFB"; 609 keySize = 64; 610 } else if (oid.equals("9") || oid.startsWith("9.")) { 611 // 1.3.14.3.2.9 - desCFB 612 cipher = "DES"; 613 mode = "CFB"; 614 keySize = 64; 615 } else if (oid.equals("17") || oid.startsWith("17.")) { 616 // 1.3.14.3.2.17 - desEDE 617 cipher = "DESede"; 618 mode = "CBC"; 619 keySize = 192; 620 621 // If the supplied IV is all zeroes, then this is DES2 622 // (Well, that's what happened when I played with OpenSSL!) 623 if (allZeroes(pkcs8.iv)) { 624 mode = "ECB"; 625 use2DES = true; 626 pkcs8.iv = null; 627 } 628 } 629 } 630 631 // AES 632 // 2.16.840.1.101.3.4.1.1 - id-aes128-ECB 633 // 2.16.840.1.101.3.4.1.2 - id-aes128-CBC 634 // 2.16.840.1.101.3.4.1.3 - id-aes128-OFB 635 // 2.16.840.1.101.3.4.1.4 - id-aes128-CFB 636 // 2.16.840.1.101.3.4.1.21 - id-aes192-ECB 637 // 2.16.840.1.101.3.4.1.22 - id-aes192-CBC 638 // 2.16.840.1.101.3.4.1.23 - id-aes192-OFB 639 // 2.16.840.1.101.3.4.1.24 - id-aes192-CFB 640 // 2.16.840.1.101.3.4.1.41 - id-aes256-ECB 641 // 2.16.840.1.101.3.4.1.42 - id-aes256-CBC 642 // 2.16.840.1.101.3.4.1.43 - id-aes256-OFB 643 // 2.16.840.1.101.3.4.1.44 - id-aes256-CFB 644 else if (oid.startsWith("2.16.840.1.101.3.4.1.")) { 645 cipher = "AES"; 646 if (pkcs8.iv == null) { 647 ivSize = 128; 648 } 649 oid = oid.substring("2.16.840.1.101.3.4.1.".length()); 650 int x = oid.indexOf('.'); 651 int finalDigit; 652 if (x >= 0) { 653 finalDigit = Integer.parseInt(oid.substring(0, x)); 654 } else { 655 finalDigit = Integer.parseInt(oid); 656 } 657 switch (finalDigit % 10) { 658 case 1: 659 mode = "ECB"; 660 break; 661 case 2: 662 mode = "CBC"; 663 break; 664 case 3: 665 mode = "OFB"; 666 break; 667 case 4: 668 mode = "CFB"; 669 break; 670 default: 671 throw new RuntimeException("Unknown AES final digit: " + finalDigit); 672 } 673 switch (finalDigit / 10) { 674 case 0: 675 keySize = 128; 676 break; 677 case 2: 678 keySize = 192; 679 break; 680 case 4: 681 keySize = 256; 682 break; 683 default: 684 throw new RuntimeException("Unknown AES final digit: " + finalDigit); 685 } 686 } else if (oid.startsWith("1.2.840.113549.3.")) { 687 // Let's trim this OID to make life a little easier. 688 oid = oid.substring("1.2.840.113549.3.".length()); 689 690 if (oid.equals("2") || oid.startsWith("2.")) { 691 // 1.2.840.113549.3.2 - RC2-CBC 692 // Note: keysize determined in PKCS8 Version 2.0 ASN.1 field. 693 cipher = "RC2"; 694 keySize = pkcs8.keySize * 8; 695 } else if (oid.equals("4") || oid.startsWith("4.")) { 696 // 1.2.840.113549.3.4 - RC4 697 // Note: keysize determined in PKCS8 Version 2.0 ASN.1 field. 698 cipher = "RC4"; 699 keySize = pkcs8.keySize * 8; 700 } else if (oid.equals("7") || oid.startsWith("7.")) { 701 // 1.2.840.113549.3.7 - DES-EDE3-CBC 702 cipher = "DESede"; 703 keySize = 192; 704 } else if (oid.equals("9") || oid.startsWith("9.")) { 705 // 1.2.840.113549.3.9 - RC5 CBC Pad 706 // Note: keysize determined in PKCS8 Version 2.0 ASN.1 field. 707 keySize = pkcs8.keySize * 8; 708 cipher = "RC5"; 709 710 // Need to find out more about RC5. 711 // How do I create the RC5ParameterSpec? 712 // (int version, int rounds, int wordSize, byte[] iv) 713 } 714 } 715 } 716 717 // The pkcs8 structure has been thoroughly examined. If we don't have 718 // a cipher or hash at this point, then we don't support the file we 719 // were given. 720 if (cipher == null || hash == null) { 721 throw new ProbablyNotPKCS8Exception("Unsupported PKCS8 format. oid1=[" + pkcs8.oid1 + "], oid2=[" + pkcs8.oid2 + "]"); 722 } 723 724 // In PKCS8 Version 1.5 we need to derive an 8 byte IV. In those cases 725 // the ASN.1 structure doesn't have the IV, anyway, so I can use that 726 // to decide whether to derive one or not. 727 // 728 // Note: if AES, then IV has to be 16 bytes. 729 if (pkcs8.iv == null) { 730 ivSize = 64; 731 } 732 733 byte[] salt = pkcs8.salt; 734 int ic = pkcs8.iterationCount; 735 736 // PKCS8 converts the password to a byte[] array using a simple 737 // cast. This byte[] array is ignored if we're using the PKCS12 738 // key derivation, since that employs a different technique. 739 byte[] pwd = new byte[password.length]; 740 for (int i = 0; i < pwd.length; i++) { 741 pwd[i] = (byte) password[i]; 742 } 743 744 DerivedKey dk; 745 if (usePKCS12PasswordPadding) { 746 MessageDigest md = MessageDigest.getInstance(hash); 747 dk = deriveKeyPKCS12(password, salt, ic, keySize, ivSize, md); 748 } else { 749 if (isVersion1) { 750 MessageDigest md = MessageDigest.getInstance(hash); 751 dk = deriveKeyV1(pwd, salt, ic, keySize, ivSize, md); 752 } else { 753 Mac mac = Mac.getInstance(hash); 754 dk = deriveKeyV2(pwd, salt, ic, keySize, ivSize, mac); 755 } 756 } 757 758 759 return decrypt(cipher, mode, dk, use2DES, pkcs8.iv, pkcs8.bigPayload); 760 } 761 762 763 public static DerivedKey deriveKeyV1(byte[] password, byte[] salt, 764 int iterations, int keySizeInBits, 765 int ivSizeInBits, MessageDigest md) { 766 int keySize = keySizeInBits / 8; 767 int ivSize = ivSizeInBits / 8; 768 md.reset(); 769 md.update(password); 770 byte[] result = md.digest(salt); 771 for (int i = 1; i < iterations; i++) { 772 // Hash of the hash for each of the iterations. 773 result = md.digest(result); 774 } 775 byte[] key = new byte[keySize]; 776 byte[] iv = new byte[ivSize]; 777 System.arraycopy(result, 0, key, 0, key.length); 778 System.arraycopy(result, key.length, iv, 0, iv.length); 779 return new DerivedKey(key, iv); 780 } 781 782 public static DerivedKey deriveKeyPKCS12(char[] password, byte[] salt, 783 int iterations, int keySizeInBits, 784 int ivSizeInBits, 785 MessageDigest md) { 786 byte[] pwd; 787 if (password.length > 0) { 788 pwd = new byte[(password.length + 1) * 2]; 789 for (int i = 0; i < password.length; i++) { 790 pwd[i * 2] = (byte) (password[i] >>> 8); 791 pwd[i * 2 + 1] = (byte) password[i]; 792 } 793 } else { 794 pwd = new byte[0]; 795 } 796 int keySize = keySizeInBits / 8; 797 int ivSize = ivSizeInBits / 8; 798 byte[] key = pkcs12(1, keySize, salt, pwd, iterations, md); 799 byte[] iv = pkcs12(2, ivSize, salt, pwd, iterations, md); 800 return new DerivedKey(key, iv); 801 } 802 803 /** 804 * This PKCS12 key derivation code comes from BouncyCastle. 805 * 806 * @param idByte 1 == key, 2 == iv 807 * @param n keysize or ivsize 808 * @param salt 8 byte salt 809 * @param password password 810 * @param iterationCount iteration-count 811 * @param md The message digest to use 812 * @return byte[] the derived key 813 */ 814 private static byte[] pkcs12(int idByte, int n, byte[] salt, 815 byte[] password, int iterationCount, 816 MessageDigest md) { 817 int u = md.getDigestLength(); 818 // sha1, md2, md5 all use 512 bits. But future hashes might not. 819 int v = 512 / 8; 820 md.reset(); 821 byte[] D = new byte[v]; 822 byte[] dKey = new byte[n]; 823 for (int i = 0; i != D.length; i++) { 824 D[i] = (byte) idByte; 825 } 826 byte[] S; 827 if ((salt != null) && (salt.length != 0)) { 828 S = new byte[v * ((salt.length + v - 1) / v)]; 829 for (int i = 0; i != S.length; i++) { 830 S[i] = salt[i % salt.length]; 831 } 832 } else { 833 S = new byte[0]; 834 } 835 byte[] P; 836 if ((password != null) && (password.length != 0)) { 837 P = new byte[v * ((password.length + v - 1) / v)]; 838 for (int i = 0; i != P.length; i++) { 839 P[i] = password[i % password.length]; 840 } 841 } else { 842 P = new byte[0]; 843 } 844 byte[] I = new byte[S.length + P.length]; 845 System.arraycopy(S, 0, I, 0, S.length); 846 System.arraycopy(P, 0, I, S.length, P.length); 847 byte[] B = new byte[v]; 848 int c = (n + u - 1) / u; 849 for (int i = 1; i <= c; i++) { 850 md.update(D); 851 byte[] result = md.digest(I); 852 for (int j = 1; j != iterationCount; j++) { 853 result = md.digest(result); 854 } 855 for (int j = 0; j != B.length; j++) { 856 B[j] = result[j % result.length]; 857 } 858 for (int j = 0; j < (I.length / v); j++) { 859 /* 860 * add a + b + 1, returning the result in a. The a value is treated 861 * as a BigInteger of length (b.length * 8) bits. The result is 862 * modulo 2^b.length in case of overflow. 863 */ 864 int aOff = j * v; 865 int bLast = B.length - 1; 866 int x = (B[bLast] & 0xff) + (I[aOff + bLast] & 0xff) + 1; 867 I[aOff + bLast] = (byte) x; 868 x >>>= 8; 869 for (int k = B.length - 2; k >= 0; k--) { 870 x += (B[k] & 0xff) + (I[aOff + k] & 0xff); 871 I[aOff + k] = (byte) x; 872 x >>>= 8; 873 } 874 } 875 if (i == c) { 876 System.arraycopy(result, 0, dKey, (i - 1) * u, dKey.length - ((i - 1) * u)); 877 } else { 878 System.arraycopy(result, 0, dKey, (i - 1) * u, result.length); 879 } 880 } 881 return dKey; 882 } 883 884 public static DerivedKey deriveKeyV2(byte[] password, byte[] salt, 885 int iterations, int keySizeInBits, 886 int ivSizeInBits, Mac mac) 887 throws InvalidKeyException { 888 int keySize = keySizeInBits / 8; 889 int ivSize = ivSizeInBits / 8; 890 891 // Because we're using an Hmac, we need to initialize with a SecretKey. 892 // HmacSHA1 doesn't need SecretKeySpec's 2nd parameter, hence the "N/A". 893 SecretKeySpec sk = new SecretKeySpec(password, "N/A"); 894 mac.init(sk); 895 int macLength = mac.getMacLength(); 896 int derivedKeyLength = keySize + ivSize; 897 int blocks = (derivedKeyLength + macLength - 1) / macLength; 898 byte[] blockIndex = new byte[4]; 899 byte[] finalResult = new byte[blocks * macLength]; 900 for (int i = 1; i <= blocks; i++) { 901 int offset = (i - 1) * macLength; 902 blockIndex[0] = (byte) (i >>> 24); 903 blockIndex[1] = (byte) (i >>> 16); 904 blockIndex[2] = (byte) (i >>> 8); 905 blockIndex[3] = (byte) i; 906 mac.reset(); 907 mac.update(salt); 908 byte[] result = mac.doFinal(blockIndex); 909 System.arraycopy(result, 0, finalResult, offset, result.length); 910 for (int j = 1; j < iterations; j++) { 911 mac.reset(); 912 result = mac.doFinal(result); 913 for (int k = 0; k < result.length; k++) { 914 finalResult[offset + k] ^= result[k]; 915 } 916 } 917 } 918 byte[] key = new byte[keySize]; 919 byte[] iv = new byte[ivSize]; 920 System.arraycopy(finalResult, 0, key, 0, key.length); 921 System.arraycopy(finalResult, key.length, iv, 0, iv.length); 922 return new DerivedKey(key, iv); 923 } 924 925 public static byte[] formatAsPKCS8(byte[] privateKey, String oid, 926 ASN1Structure pkcs8) { 927 ASN1Integer derZero = new ASN1Integer(BigInteger.ZERO); 928 ASN1EncodableVector outterVec = new ASN1EncodableVector(); 929 ASN1EncodableVector innerVec = new ASN1EncodableVector(); 930 DEROctetString octetsToAppend; 931 try { 932 ASN1ObjectIdentifier derOID = new ASN1ObjectIdentifier(oid); 933 innerVec.add(derOID); 934 if (DSA_OID.equals(oid)) { 935 if (pkcs8 == null) { 936 try { 937 pkcs8 = ASN1Util.analyze(privateKey); 938 } 939 catch (Exception e) { 940 throw new RuntimeException("asn1 parse failure " + e); 941 } 942 } 943 if (pkcs8.derIntegers == null || pkcs8.derIntegers.size() < 6) { 944 throw new RuntimeException("invalid DSA key - can't find P, Q, G, X"); 945 } 946 947 ASN1Integer[] ints = new ASN1Integer[pkcs8.derIntegers.size()]; 948 pkcs8.derIntegers.toArray(ints); 949 ASN1Integer p = ints[1]; 950 ASN1Integer q = ints[2]; 951 ASN1Integer g = ints[3]; 952 ASN1Integer x = ints[5]; 953 954 byte[] encodedX = encode(x); 955 octetsToAppend = new DEROctetString(encodedX); 956 ASN1EncodableVector pqgVec = new ASN1EncodableVector(); 957 pqgVec.add(p); 958 pqgVec.add(q); 959 pqgVec.add(g); 960 DERSequence pqg = new DERSequence(pqgVec); 961 innerVec.add(pqg); 962 } else { 963 innerVec.add(DERNull.INSTANCE); 964 octetsToAppend = new DEROctetString(privateKey); 965 } 966 967 DERSequence inner = new DERSequence(innerVec); 968 outterVec.add(derZero); 969 outterVec.add(inner); 970 outterVec.add(octetsToAppend); 971 DERSequence outter = new DERSequence(outterVec); 972 return encode(outter); 973 } 974 catch (IOException ioe) { 975 throw JavaImpl.newRuntimeException(ioe); 976 } 977 } 978 979 private static boolean allZeroes(byte[] b) { 980 for (int i = 0; i < b.length; i++) { 981 if (b[i] != 0) { 982 return false; 983 } 984 } 985 return true; 986 } 987 988 public static byte[] encode(ASN1Encodable der) throws IOException { 989 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); 990 ASN1OutputStream out = new ASN1OutputStream(baos); 991 out.writeObject(der); 992 out.close(); 993 return baos.toByteArray(); 994 } 995 996 public static void main(String[] args) throws Exception { 997 String password = "changeit"; 998 if (args.length == 0) { 999 System.out.println("Usage1: [password] [file:private-key] Prints decrypted PKCS8 key (base64)."); 1000 System.out.println("Usage2: [password] [file1] [file2] etc... Checks that all private keys are equal."); 1001 System.out.println("Usage2 assumes that all files can be decrypted with the same password."); 1002 } else if (args.length == 1 || args.length == 2) { 1003 FileInputStream in = new FileInputStream(args[args.length - 1]); 1004 if (args.length == 2) { 1005 password = args[0]; 1006 } 1007 byte[] bytes = Util.streamToBytes(in); 1008 PKCS8Key key = new PKCS8Key(bytes, password.toCharArray()); 1009 PEMItem item = new PEMItem(key.getDecryptedBytes(), "PRIVATE KEY"); 1010 byte[] pem = PEMUtil.encode(Collections.singleton(item)); 1011 System.out.write(pem); 1012 } else { 1013 byte[] original = null; 1014 File f = new File(args[0]); 1015 int i = 0; 1016 if (!f.exists()) { 1017 // File0 doesn't exist, so it must be a password! 1018 password = args[0]; 1019 i++; 1020 } 1021 for (; i < args.length; i++) { 1022 FileInputStream in = new FileInputStream(args[i]); 1023 byte[] bytes = Util.streamToBytes(in); 1024 PKCS8Key key = null; 1025 try { 1026 key = new PKCS8Key(bytes, password.toCharArray()); 1027 } 1028 catch (Exception e) { 1029 System.out.println(" FAILED! " + args[i] + " " + e); 1030 } 1031 if (key != null) { 1032 byte[] decrypted = key.getDecryptedBytes(); 1033 int keySize = key.getKeySize(); 1034 String keySizeStr = "" + keySize; 1035 if (keySize < 10) { 1036 keySizeStr = " " + keySizeStr; 1037 } else if (keySize < 100) { 1038 keySizeStr = " " + keySizeStr; 1039 } 1040 StringBuffer buf = new StringBuffer(key.getTransformation()); 1041 int maxLen = "Blowfish/CBC/PKCS5Padding".length(); 1042 for (int j = buf.length(); j < maxLen; j++) { 1043 buf.append(' '); 1044 } 1045 String transform = buf.toString(); 1046 String type = key.isDSA() ? "DSA" : "RSA"; 1047 1048 if (original == null) { 1049 original = decrypted; 1050 System.out.println(" SUCCESS \t" + type + "\t" + transform + "\t" + keySizeStr + "\t" + args[i]); 1051 } else { 1052 boolean identical = Arrays.equals(original, decrypted); 1053 if (!identical) { 1054 System.out.println("***FAILURE*** \t" + type + "\t" + transform + "\t" + keySizeStr + "\t" + args[i]); 1055 } else { 1056 System.out.println(" SUCCESS \t" + type + "\t" + transform + "\t" + keySizeStr + "\t" + args[i]); 1057 } 1058 } 1059 } 1060 } 1061 } 1062 } 1063 1064}