كيف يمكنني فك تشفير مفتاح PKCS#5 المشفر PKCS#8 الخاص في Java

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

سؤال

لدي مفتاح خاص PKCS#5 مشفر PKCS#8 RSA مخزن في ملف قرص (تم إنشاؤه في الأصل بواسطة SSLPlus، حوالي عام 1997)، على سبيل المثال:

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

أحتاج للحصول على كائن Java Key والذي يمكنني بعد ذلك إضافته مع الشهادة المطابقة إلى KeyStore.يتم تشفير المفتاح الخاص بمفتاح ثنائي بحجم 100 بايت.

كان إنشاء كائن الشهادة أمرًا بسيطًا، لكن يبدو أنني لا أستطيع معرفة كيفية الانتقال من مفتاح PKCS#5 المشفر باستخدام Base64 أعلاه إلى المفتاح الخاص PKCS#8 RSA الذي تم فك تشفيره.في هذه المرحلة أنا في وضع حرج بسبب فشل استدعاء SecretKeyFactory.generateSecret() مع:

InvalidKeySpecException: Password is not ASCII

الآن، صحيح أن كلمة المرور ليست ASCII، بالمعنى الدقيق للكلمة وهي 0x00 إلى 0x7F، لكن خوارزمية PBEWithMD5AndDES يجب أن تقبل قيم الأحرف من 0x00 إلى 0xFF.

هل يمكن لأي شخص أن يوضح لي كيفية الوصول من القيمة المشفرة Base64 إلى كائن رئيسي يمكنني إضافته إلى مخزن المفاتيح؟


خاتمة

يقبل PBEKey الصادر مع Java كلمة مرور ذات قيم ASCII في النطاق 0x20<=char<=0x7E فقط.تم حل هذه المشكلة المتعلقة بكلمة المرور غير ASCII عن طريق إنشاء BinaryPBEKey الخاص بي والذي يسمح بقيم البايت من 0x00 إلى 0xFF (انظر أدناه).

كانت المشكلة اللاحقة التي واجهتني هي أن بيانات PKCS#8 الخاصة بي لم يتم تشفيرها بشكل صحيح (وهو خطأ شائع على ما يبدو في عمليات التنفيذ المبكرة لـ SSL)، حيث يلزم تغليف بيانات PKCS#1 في سلسلة ASN.1 ثماني بتات.لقد قمت بكتابة روتين ترقيع بسيط يتعامل مع مفاتيحي، والتي من المعروف أن طولها يتراوح بين 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 حتى يتم تشفير البنية بشكل صحيح.

جرب هذا بدلاً من ذلك:30820277020100300D06092A864886F70D0101010500048202613082025D02010002818100CA72B8D1B88EB939C092C14C53B4F438483FC31CDC6BBCBE26A3B 2F77C60A82C0D86EDFC2DD25C99B6B671A86D2F5125FA9C42FE10C12F39EAE8FF1A78BA6B64B839343BF41C4506C3B998DC01FF4156364FDD3569A427BB5FFDDDD5C73BA9A9 45A4F37A9483D5B89EAEEBA8D026ED76ED46FBC7D7AA4414C4DCA08052066A3EB0203010001028180216AE27B2BDDD351672A526209073BB0F6AC1FC6E9D396EA44728D1E3 117BB6ADA28C5ABF4DC5E90B90A50A49EB14AD1DC166330910F727E3AFA8EF18DB027FDC2BAB5F8FC7C46C0FDADA7397C36717A338BAD0D0CDA50B70EBFD8647D44BD646FE251B75 E2D7BBA02DBA62F2088669885342EEFD42961237987272755158D21024100F962BD224AC8567AC317EBCECC5F42E140F5A566603254866726AD7C34C2FEFE8AF77FBE79535 FC973D9478B0F89A109F12716FCF14BC3A92759290DDA9CAE53024100CFD14A31509AB4BA90422549547C20542ECFE8F135DA92C2A3949DB7B1853F13D0CABC77D98AF3328 35993E1F0111B4CE5A23050FE1FB68DA5B144DA4D4B1109024046533AC49DD40AD70987085F43B0A55A8208038170252142D979C5B85DE49325D2A862A4A2F008F5 F52E53877A75342D6A8CBC65CDE1B0A655CB45D17B516DB302410081CC617F9DAD92F5F78628CDBD43EDD94687BB2175167895B31FEEC63DCD50916AD64592C1C02497C72 C5ACE42681CDA118F148871C092FFB39E9DB78F913429024100887A99ACAAA9D52B6EE1870AE8D24C048EA2EA003F8DAF9F766186B01D1869C86422D46BA3A4BB52B1AC38DB 6B5C28F078733E37FDC85472C7FDA9EBC9F24596

تم تحليلها:

   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) ستكون ترميزات طويلة بأحد الأشكال التالية

  • الطول <=0x7F: XX, ، حيث XX هو الطول
  • 0x80<=الطول<=0xFF: 81XX, ، حيث XX هو الطول
  • 0x0100<=الطول<=0xFFFF: 82XXXX, ، حيث XXXX هو الطول
  • 0x010000<=الطول<=0xFFFFFF: 83XXXXXX,

إذا كانت مفاتيحك كلها بنفس الطول، فمن المحتمل أن تفترض أن ترميزات الطول ستكون دائمًا في النموذج 82XXXX, ولكن من المحتمل أن تختلف الأطوال الفعلية.

اقرأ الطول في (*2), ، أضف الطول بالبايت 30(*2) إلى الرقم (ربما يكون هذا 4) وقم بتشفير الطول على النحو الوارد أعلاه (سيكون على الأرجح النموذج 82XXXX).دعونا نسمي هذا ترميز الطول (*3).إدراج 04(*3) قبل 30(*2).أضف الآن طول 04(*3) (وربما أيضًا 4) ل (*1) وأعد ترميز هذا (من المحتمل أن يظل مناسبًا 82XXXX) واستبدال (*1) مع هذا.

أتمنى أن يكون الأمر مفهوما، وإلا أنصح بالقراءة دليل المواطن العادي لمجموعة فرعية من ASN.1 وBER وDER.

نصائح أخرى

هل حاولت فتح المفتاح مع بعض الفئات الداخلية لـ Bouncy Castle؟ربما باستخدامها مباشرةً بدلاً من مجرد تعريف BC كموفر تشفير، يمكنك تحليل هذا الملف...

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top