JavaでPKCS#5暗号化PKCS#8秘密鍵をデコードするにはどうすればよいですか

StackOverflow https://stackoverflow.com/questions/809590

質問

PKCS#5で暗号化されたPKCS#8 RSA秘密キーをディスクファイルに保存しています(元々1997年頃にSSLPlusによって生成されました)。例:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICmDAaBgkqhkiG9w0BBQMwDQQIybM2XFqx4EwCAQUEggJ4MKg/NE+L6NJgbOf4
...
8QnGu4R7lFlweH/VAK8n0L75h3q2g62MKLJqmKLtAILNve4zymnO+LVZ4Js=
-----END ENCRYPTED PRIVATE KEY-----

Javaキーオブジェクトを取得する必要があるため、一致する証明書とともにキーストアに追加できます。秘密鍵は100バイトのバイナリキーで暗号化されます。

証明書オブジェクトの作成は簡単でしたが、上記のBase64エンコードPKCS#5キーから復号化されたPKCS#8 RSA秘密キーに移行する方法がわかりません。 SecretKeyFactory.generateSecret()呼び出しが次のように失敗するため、この時点でAt地に陥ります。

InvalidKeySpecException: Password is not ASCII

現在、パスワードが0x00から0x7Fの厳密な意味でASCIIではないのは事実ですが、PBEWithMD5AndDESアルゴリズムは0x00から0xFFの文字値を受け入れる必要があります。

Base64でエンコードされた値からキーストアに追加できるKeyオブジェクトに取得する方法を教えてもらえますか?


結論

Javaで発行されたPBEKeyは、0x20 <!> lt; = char <!> lt; = 0x7Eの範囲のASCII値を持つパスワードのみを受け入れます。 ASCII以外のパスワードに関するこの問題は、0x00〜0xFFのバイト値を許可する独自のBinaryPBEKeyを作成することで解決しました(以下を参照)。

その後の問題は、PKCS#1データをASN.1オクテット文字列でラップする必要があるという点で、PKCS#8データが適切にエンコードされていないことです(SSLの初期の実装ではよくある間違いです)。私は自分のキーを処理する簡単なパッチ適用ルーチンを作成しました。これは512〜4096ビットの長さであることが知られています(以下を参照)。


プライベートキーデコーダー

private PrivateKey readPrivateKey(File inpfil) throws IOException, GeneralSecurityException {
    String[]                            pbeb64s;                                // PBE ASN.1 data base-64 encoded

    byte[]                              pbedta;                                 // PBE ASN.1 data in bytes
    EncryptedPrivateKeyInfo             pbeinf;                                 // PBE key info
    PBEParameterSpec                    pbeprm;                                 // PBE parameters
    Cipher                              pbecph;                                 // PBE decryption cipher

    byte[]                              pk8dta;                                 // PKCS#8 ASN.1 data in bytes
    KeyFactory                          pk8fac=KeyFactory.getInstance("RSA");   // PKCS#8 key factory for decoding private key from ASN.1 data.

    pbeb64s=readDataBlocks(inpfil,"ENCRYPTED PRIVATE KEY");
    if(pbeb64s.length!=1) { throw new GeneralSecurityException("The keystore '"+inpfil+"' contains multiple private keys"); }
    pbedta=base64.decode(pbeb64s[0]);
    log.diagln("  - Read private key data");

    pbeinf=new EncryptedPrivateKeyInfo(pbedta);
    pbeprm=(PBEParameterSpec)pbeinf.getAlgParameters().getParameterSpec(PBEParameterSpec.class);
    pbecph=Cipher.getInstance(pbeinf.getAlgName());
    pbecph.init(Cipher.DECRYPT_MODE,pbeDecryptKey,pbeprm);

    pk8dta=pbecph.doFinal(pbeinf.getEncryptedData());
    log.diagln("  - Private Key: Algorithm= "+pbeinf.getAlgName()+", Iterations: "+pbeprm.getIterationCount()+", Salt: "+Base16.toString(pbeprm.getSalt()));
    pk8dta=patchKeyData(inpfil,pk8dta);
    return pk8fac.generatePrivate(new PKCS8EncodedKeySpec(pk8dta));
    }

BinaryPBEKey

import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;

class BinaryPBEKey
extends Object
implements SecretKey
{
private final byte[]                    key;

/**
 * Creates a PBE key from a given binary key.
 *
 * @param key       The key.
 */
BinaryPBEKey(byte[] key) throws InvalidKeySpecException {
    if(key==null) { this.key=new byte[0];         }
    else          { this.key=(byte[])key.clone(); }
    Arrays.fill(key,(byte)0);
    }

public byte[] getEncoded() {
    return (byte[])key.clone();
    }

public String getAlgorithm() {
    return "PBEWithMD5AndDES";
    }

public String getFormat() {
    return "RAW";
    }

/**
 * Calculates a hash code value for the object.
 * Objects that are equal will also have the same hashcode.
 */
public int hashCode() {
    int                             ret=0;

    for(int xa=1; xa<this.key.length; xa++) { ret+=(this.key[xa]*xa); }
    return (ret^=getAlgorithm().toLowerCase().hashCode());
    }

public boolean equals(Object obj) {
    if(obj==this                 ) { return true;  }
    if(obj.getClass()!=getClass()) { return false; }

    BinaryPBEKey                    oth=(BinaryPBEKey)obj;

    if(!(oth.getAlgorithm().equalsIgnoreCase(getAlgorithm()))) {
        return false;
        }

    byte[]  othkey=oth.getEncoded();
    boolean ret   =Arrays.equals(key,othkey);
    Arrays.fill(othkey,(byte)0);
    return ret;
    }

public void destroy() {
    Arrays.fill(this.key,(byte)0);
    }

/**
 * Ensure that the password bytes of this key are zeroed out when there are no more references to it.
 */
protected void finalize() throws Throwable {
    try { destroy(); } finally { super.finalize(); }
    }

PKCS#8パッチ適用

/**
 * Patch the private key ASN.1 data to conform to PKCS#8.
 * <p>
 * The SSLPlus private key is not properly encoded PKCS#8 - the PKCS#1 RSAPrivateKey should have been wrapped
 * inside an OctetString, thus:
 * <pre>
 * SSLPlus Encoding:
 *        0 30  627: SEQUENCE {
 *        4 02    1:   INTEGER 0
 *        7 30   13:   SEQUENCE {
 *        9 06    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
 *       20 05    0:     NULL
 *                 :     }
 *       22 30  605:   SEQUENCE {
 *       26 02    1:     INTEGER 0
 *       29 02  129:     INTEGER
 *                 :       00 CA 72 B8 D1 B8 8E B9 39 C0 92 C1 4C 53 B4 F4
 *       ...
 *
 * PKCS#8 Encoding
 *       0 30  631: SEQUENCE {
 *       4 02    1:   INTEGER 0
 *       7 30   13:   SEQUENCE {
 *       9 06    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
 *      20 05    0:     NULL
 *                :     }
 * ==>  22 04  609:   OCTET STRING, encapsulates {
 *      26 30  605:       SEQUENCE {
 *      30 02    1:         INTEGER 0
 *      33 02  129:         INTEGER
 *                :           00 CA 72 B8 D1 B8 8E B9 39 C0 92 C1 4C 53 B4 F4
 *      ...
 * </pre>
 *
 * Hex Dumps (1K key, space padded for clarity):
 *    Before      : 30 820271 020100300D06092A864886F70D0101010500           30 82025B ... A228
 *    After       : 30 820275 020100300D06092A864886F70D0101010500 04 82025F 30 82025B ... A228
 *                     ^^^^^^                                         ^^^^^^
 *                     Add 4 for later 0482xxxx                       Original total + 4 - 22 (equals the key length of 025B+4)
 */
private byte[] patchKeyData(File inpfil, byte[] asndta) throws IOException, GeneralSecurityException { // except it really doesn't throw an exception
    ByteArrayOutputStream               patdta=new ByteArrayOutputStream();
    int                                 orglen=decodeAsnLength(inpfil,asndta,1);

    patdta.write(asndta,0,1);                                                   // original leader type
    patdta.write(encodeAsnLength(inpfil,(orglen+4)));                           // new total length
    patdta.write(asndta,4,(22-4));                                              // bit between total length an where octet-string wrapper needs to be inserted
    patdta.write(0x04);                                                         // octet-string type
    patdta.write(encodeAsnLength(inpfil,(orglen+4-22)));                        // octet-string length (key data type+key data length+key data)
    patdta.write(asndta,22,asndta.length-22);                                   // private key data
    return patdta.toByteArray();
    }

private int decodeAsnLength(File inpfil, byte[] asndta, int ofs) throws GeneralSecurityException {
    if((asndta[ofs]&0xFF)==0x82) { return (((asndta[ofs+1]&0x000000FF)<< 8)|((asndta[ofs+2]&0x000000FF)));                                                           }
    else                         { throw new GeneralSecurityException("The private key in file '"+inpfil+"' is not supported (ID="+Base16.toString(asndta,0,4)+")"); }
    }

private byte[] encodeAsnLength(File inpfil, int len) throws GeneralSecurityException {
    if(len>=0x0100 && len<=0xFFFF) { return new byte[]{ (byte)0x82,(byte)((len>>>8)&0x000000FF),(byte)len };                                                            }
    else                           { throw new GeneralSecurityException("The new length of "+len+" for patching the private key in file '"+inpfil+"' is out of range"); }
    }
役に立ちましたか?

解決

復号化したデータをASN.1パーサーにダンプしましたが、ASN.1はまったく問題ありません。

       0 30  627: SEQUENCE {
       4 02    1:   INTEGER 0
       7 30   13:   SEQUENCE {
       9 06    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
      20 05    0:     NULL
                :     }
      22 30  605:   SEQUENCE {
      26 02    1:     INTEGER 0
      29 02  129:     INTEGER
                :       00 CA 72 B8 D1 B8 8E B9 39 C0 92 C1 4C 53 B4 F4
                :       38 48 3F C3 1C DC 6B BC BE 26 A3 B2 F7 7C 60 A8
                :       2C 0D 86 ED FC 2D D2 5C 99 B6 B6 71 A8 6D 2F 51
                :       25 FA 9C 42 FE 10 C1 2F 39 EA E8 FF 1A 78 BA 6B
                :       64 B8 39 34 3B F4 1C 45 06 C3 B9 98 DC 01 FF 41
                :       56 36 4F DD 35 69 A4 27 BB 5F FD DD 5C 73 BA 9A
                :       94 5A 4F 37 A9 48 3D 5B 89 EA EE BA 8D 02 6E D7
                :       6E D4 6F BC 7D 7A A4 41 4C 4D CA 08 05 20 66 A3
                :               [ Another 1 bytes skipped ]
     161 02    3:     INTEGER 65537
     166 02  128:     INTEGER
                :       21 6A E2 7B 2B DD D3 51 67 2A 52 62 09 07 3B B0
                :       F6 AC 1F C6 E9 D3 96 EA 44 72 8D 1E 31 17 BB 6A
                :       DA 28 C5 AB F4 DC 5E 90 B9 0A 50 A4 9E B1 4A D1
                :       DC 16 63 30 91 0F 72 7E 3A FA 8E F1 8D B0 27 FD
                :       C2 BA B5 F8 FC 7C 46 C0 FD AD A7 39 7C 36 71 7A
                :       33 8B AD 0D 0C DA 50 B7 0E BF D8 64 7D 44 BD 64
                :       6F E2 51 B7 5E 2D 7B BA 02 DB A6 2F 20 88 66 98
                :       85 34 2E EF D4 29 61 23 79 87 27 27 55 15 8D 21
     297 02   65:     INTEGER
                :       00 F9 62 BD 22 4A C8 56 7A C3 17 EB CE CC 5F 42
                :       E1 40 F5 A5 66 60 32 54 86 67 26 AD 7C 34 C2 FE
                :       FE 8A F7 7F BE 79 53 5F C9 73 D9 47 8B 0F 89 A1
                :       09 F1 27 16 FC F1 4B C3 A9 27 59 29 0D DA 9C AE
                :       53
     364 02   65:     INTEGER
                :       00 CF D1 4A 31 50 9A B4 BA 90 42 25 49 54 7C 20
                :       54 2E CF E8 F1 35 DA 92 C2 A3 94 9D B7 B1 85 3F
                :       13 D0 CA BC 77 D9 8A F3 32 83 59 93 E1 F0 11 1B
                :       4C E5 A2 30 50 FE 1F B6 8D A5 B1 44 DA 4D 4B 11
                :       09
     431 02   64:     INTEGER
                :       46 53 3A C4 9D D4 0A D7 09 87 08 5F 43 B0 A5 5A
                :       82 08 03 81 70 25 21 42 D9 79 C5 B8 5D E4 93 25
                :       D2 A8 62 A4 A2 F0 08 F5 F5 2E 53 87 7A 75 34 2D
                :       6A 8C BC 65 CD E1 B0 A6 55 CB 45 D1 7B 51 6D B3
     497 02   65:     INTEGER
                :       00 81 CC 61 7F 9D AD 92 F5 F7 86 28 CD BD 43 ED
                :       D9 46 87 BB 21 75 16 78 95 B3 1F EE C6 3D CD 50
                :       91 6A D6 45 92 C1 C0 24 97 C7 2C 5A CE 42 68 1C
                :       DA 11 8F 14 88 71 C0 92 FF B3 9E 9D B7 8F 91 34
                :       29
     564 02   65:     INTEGER
                :       00 88 7A 99 AC AA A9 D5 2B 6E E1 87 0A E8 D2 4C
                :       04 8E A2 EA 00 3F 8D AF 9F 76 61 86 B0 1D 18 69
                :       C8 64 22 D4 6B A3 A4 BB 52 B1 AC 38 DB 6B 5C 28
                :       F0 78 73 3E 37 FD C8 54 72 C7 FD A9 EB C9 F2 45
                :       96
                :     }
                :   }

残念ながら、正しくエンコードされたPKCS#8 PrivateKeyInfoではありません。インデックス22から始まるシーケンスはPKCS#1 PKCS1RSAPrivateKeyであり、構造が正しくエンコードされるようにOctetString内にラップされている必要があります。

代わりにこれを試してください:         30820277020100300D06092A864886F70D0101010500048202613082025D02010002818100CA72B8D1B88EB939C092C14C53B4F438483FC31CDC6BBCBE26A3B2F77C60A82C0D86EDFC2DD25C99B6B671A86D2F5125FA9C42FE10C12F39EAE8FF1A78BA6B64B839343BF41C4506C3B998DC01FF4156364FDD3569A427BB5FFDDD5C73BA9A945A4F37A9483D5B89EAEEBA8D026ED76ED46FBC7D7AA4414C4DCA08052066A3EB0203010001028180216AE27B2BDDD351672A526209073BB0F6AC1FC6E9D396EA44728D1E3117BB6ADA28C5ABF4DC5E90B90A50A49EB14AD1DC166330910F727E3AFA8EF18DB027FDC2BAB5F8FC7C46C0FDADA7397C36717A338BAD0D0CDA50B70EBFD8647D44BD646FE251B75E2D7BBA02DBA62F2088669885342EEFD42961237987272755158D21024100F962BD224AC8567AC317EBCECC5F42E140F5A566603254866726AD7C34C2FEFE8AF77FBE79535FC973D9478B0F89A109F12716FCF14BC3A92759290DDA9CAE53024100CFD14A31509AB4BA90422549547C20542ECFE8F135DA92C2A3949DB7B1853F13D0CABC77D98AF332835993E1F0111B4CE5A23050FE1FB68DA5B144DA4D4B1109024046533AC49DD40AD70987085F43B0A55A8208038170252142D979C5B85DE49325D2A862A4A2F008F5F52E53877A75342D6A8CBC65CDE1B0A655CB45D17B516D B302410081CC617F9DAD92F5F78628CDBD43EDD94687BB2175167895B31FEEC63DCD50916AD64592C1C02497C72C5ACE42681CDA118F148871C092FFB39E9DB78F913429024100887A99ACAAA9D52B6EE1870AE8D24C048EA2EA003F8DAF9F766186B01D1869C86422D46BA3A4BB52B1AC38DB6B5C28F078733E37FDC85472C7FDA9EBC9F24596

解析済み:

   0 30  631: SEQUENCE {
   4 02    1:   INTEGER 0
   7 30   13:   SEQUENCE {
   9 06    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
  20 05    0:     NULL
            :     }
  22 04  609:   OCTET STRING, encapsulates {
  26 30  605:       SEQUENCE {
  30 02    1:         INTEGER 0
  33 02  129:         INTEGER
            :           00 CA 72 B8 D1 B8 8E B9 39 C0 92 C1 4C 53 B4 F4
            :           38 48 3F C3 1C DC 6B BC BE 26 A3 B2 F7 7C 60 A8
            :           2C 0D 86 ED FC 2D D2 5C 99 B6 B6 71 A8 6D 2F 51
            :           25 FA 9C 42 FE 10 C1 2F 39 EA E8 FF 1A 78 BA 6B
            :           64 B8 39 34 3B F4 1C 45 06 C3 B9 98 DC 01 FF 41
            :           56 36 4F DD 35 69 A4 27 BB 5F FD DD 5C 73 BA 9A
            :           94 5A 4F 37 A9 48 3D 5B 89 EA EE BA 8D 02 6E D7
            :           6E D4 6F BC 7D 7A A4 41 4C 4D CA 08 05 20 66 A3
            :                   [ Another 1 bytes skipped ]
 165 02    3:         INTEGER 65537
 170 02  128:         INTEGER
            :           21 6A E2 7B 2B DD D3 51 67 2A 52 62 09 07 3B B0
            :           F6 AC 1F C6 E9 D3 96 EA 44 72 8D 1E 31 17 BB 6A
            :           DA 28 C5 AB F4 DC 5E 90 B9 0A 50 A4 9E B1 4A D1
            :           DC 16 63 30 91 0F 72 7E 3A FA 8E F1 8D B0 27 FD
            :           C2 BA B5 F8 FC 7C 46 C0 FD AD A7 39 7C 36 71 7A
            :           33 8B AD 0D 0C DA 50 B7 0E BF D8 64 7D 44 BD 64
            :           6F E2 51 B7 5E 2D 7B BA 02 DB A6 2F 20 88 66 98
            :           85 34 2E EF D4 29 61 23 79 87 27 27 55 15 8D 21
 301 02   65:         INTEGER
            :           00 F9 62 BD 22 4A C8 56 7A C3 17 EB CE CC 5F 42
            :           E1 40 F5 A5 66 60 32 54 86 67 26 AD 7C 34 C2 FE
            :           FE 8A F7 7F BE 79 53 5F C9 73 D9 47 8B 0F 89 A1
            :           09 F1 27 16 FC F1 4B C3 A9 27 59 29 0D DA 9C AE
            :           53
 368 02   65:         INTEGER
            :           00 CF D1 4A 31 50 9A B4 BA 90 42 25 49 54 7C 20
            :           54 2E CF E8 F1 35 DA 92 C2 A3 94 9D B7 B1 85 3F
            :           13 D0 CA BC 77 D9 8A F3 32 83 59 93 E1 F0 11 1B
            :           4C E5 A2 30 50 FE 1F B6 8D A5 B1 44 DA 4D 4B 11
            :           09
 435 02   64:         INTEGER
            :           46 53 3A C4 9D D4 0A D7 09 87 08 5F 43 B0 A5 5A
            :           82 08 03 81 70 25 21 42 D9 79 C5 B8 5D E4 93 25
            :           D2 A8 62 A4 A2 F0 08 F5 F5 2E 53 87 7A 75 34 2D
            :           6A 8C BC 65 CD E1 B0 A6 55 CB 45 D1 7B 51 6D B3
 501 02   65:         INTEGER
            :           00 81 CC 61 7F 9D AD 92 F5 F7 86 28 CD BD 43 ED
            :           D9 46 87 BB 21 75 16 78 95 B3 1F EE C6 3D CD 50
            :           91 6A D6 45 92 C1 C0 24 97 C7 2C 5A CE 42 68 1C
            :           DA 11 8F 14 88 71 C0 92 FF B3 9E 9D B7 8F 91 34
            :           29
 568 02   65:         INTEGER
            :           00 88 7A 99 AC AA A9 D5 2B 6E E1 87 0A E8 D2 4C
            :           04 8E A2 EA 00 3F 8D AF 9F 76 61 86 B0 1D 18 69
            :           C8 64 22 D4 6B A3 A4 BB 52 B1 AC 38 DB 6B 5C 28
            :           F0 78 73 3E 37 FD C8 54 72 C7 FD A9 EB C9 F2 45
            :           96
            :         }
            :       }
            :   }

ファイルを修正するには、ASN.1ライブラリを使用できます(ただし、Javaに適したライブラリを認識していません)。または、以下を実行します。

データが次で始まることを確認します     30(*1)020100300D06092A864886F70D010101050030(*2) (*1)および(*2)は、次のいずれかの形式の長さエンコードになります

  • length <!> lt; = 0x7F:XX、XXは長さです
  • 0x80 <!> lt; = length <!> lt; = 0xFF:81XX、XXは長さです
  • 0x0100 <!> lt; = length <!> lt; = 0xFFFF:82XXXX、XXXXは長さです
  • 0x010000 <!> lt; = length <!> lt; = 0xFFFFFF:83XXXXXX、XXXXXXは長さ など

キーがすべて同じ長さである場合、長さエンコードは常に30(*2)の形式であると想定できますが、実際の長さはおそらく異なります。

(*3)の長さを読み取り、数値に04(*3)のバイト単位の長さを追加し(これはおそらく4)、上記のように長さをエンコードします(おそらく<=>の形式になります)。これをlength-encoding <=>と呼びましょう。 <=>の直前に<=>を挿入します。 <=>の長さ(おそらく4)を<=>に追加し、これを再エンコードし(おそらく<=>に収まる可能性があります)、<=>をこれに置き換えます。

それが理解可能であったことを望みます。そうでなければ、レイマンズガイドのサブセットASN.1、BER、およびDER

他のヒント

いくつかのBouncy Castle内部クラスでキーを開いてみましたか? BCを暗号プロバイダーとして定義するのではなく、直接使用することにより、ファイルを解析できます...

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top