如何在java中手动生成CSR

问题描述 投票:0回答:1

我正在尝试在 Java 中生成证书签名请求 (CSR),而不使用 BouncyCastle 等第三方库。以下是我用来生成 CSR 的代码:

public void createCSR(@NonNull KeyPair keyPair, @NonNull CSRCallback csrCallback) {
    try {
        byte[] subject = encodeSubject("CN=MyClient, O=MyOrganization, L=City, C=US");
        byte[] publicKey = encodePublicKey(keyPair.getPublic());
        byte[] algorithmIdentifier = encodeAlgorithmIdentifier();
        byte[] dataToSign = concatenateData(subject, publicKey, algorithmIdentifier);
        byte[] signature = signData(dataToSign, keyPair.getPrivate());
        byte[] csrBytes = createCSR(subject, publicKey, algorithmIdentifier, signature);
        String pemCSR = toPEM(csrBytes);

        csrCallback.onCSRGenerated(pemCSR);
    } catch (Exception e) {
        csrCallback.onCSRGenerationFailed(e);
    }
}

byte[] encodeSubject(String subjectDN) throws Exception {
    // Example subject: "CN=MyClient, O=MyOrganization, L=City, C=US"
    String[] rdnPairs = subjectDN.split(",\\s*");
    ByteArrayOutputStream subjectStream = new ByteArrayOutputStream();

    for (String rdn : rdnPairs) {
        String[] keyValue = rdn.split("=");
        String rdnType = keyValue[0];
        String rdnValue = keyValue[1];

        ByteArrayOutputStream rdnStream = new ByteArrayOutputStream();
        rdnStream.write(0x0C); // UTF8String tag
        rdnStream.write(rdnValue.length());
        rdnStream.write(rdnValue.getBytes(StandardCharsets.UTF_8));

        ByteArrayOutputStream rdnPairStream = new ByteArrayOutputStream();
        rdnPairStream.write(0x31); // SET tag
        rdnPairStream.write(rdnStream.size());
        rdnPairStream.write(rdnStream.toByteArray());

        subjectStream.write(0x30); // SEQUENCE tag
        subjectStream.write(rdnPairStream.size());
        subjectStream.write(rdnPairStream.toByteArray());
    }

    ByteArrayOutputStream finalSubjectStream = new ByteArrayOutputStream();
    finalSubjectStream.write(0x30); // SEQUENCE tag
    finalSubjectStream.write(subjectStream.size());
    finalSubjectStream.write(subjectStream.toByteArray());

    return finalSubjectStream.toByteArray();
}


byte[] encodePublicKey(PublicKey publicKey) {
    return publicKey.getEncoded(); // Returns the public key in X.509 DER format
}

byte[] encodeAlgorithmIdentifier() {
    // OID for RSA encryption (1.2.840.113549.1.1.11 for SHA256withRSA)
    return new byte[]{
            0x30, 0x0D, 0x06, 0x09,
            0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x0B, // OID for SHA256withRSA
            0x05, 0x00 // NULL parameter
    };
}

byte[] concatenateData(byte[] subject, byte[] publicKey, byte[] algorithmIdentifier) throws Exception {
    ByteArrayOutputStream data = new ByteArrayOutputStream();
    data.write(subject);
    data.write(publicKey);
    data.write(algorithmIdentifier);
    return data.toByteArray();
}

byte[] signData(byte[] data, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA/PSS");
    signature.initSign(privateKey);
    signature.update(data);
    return signature.sign();
}


byte[] createCSR(byte[] subject, byte[] publicKey, byte[] algorithmIdentifier, byte[] signature) throws Exception {
    ByteArrayOutputStream csrStream = new ByteArrayOutputStream();

    // SEQUENCE for CSR
    csrStream.write(0x30);
    int startPos = csrStream.size(); // Save the position for length

    // Version: INTEGER 0
    csrStream.write(0x02); // INTEGER tag
    csrStream.write(0x01); // Length
    csrStream.write(0x00); // Version value

    // Subject
    csrStream.write(subject);

    // Public Key Info
    csrStream.write(publicKey);

    // Signature Algorithm
    csrStream.write(algorithmIdentifier);

    // Signature
    csrStream.write(0x03); // BIT STRING tag
    csrStream.write(signature.length + 1); // Length
    csrStream.write(0x00); // Unused bits
    csrStream.write(signature);

    // Update the length field
    byte[] csrBytes = csrStream.toByteArray();
    int length = csrBytes.length - startPos;
    csrBytes[startPos - 1] = (byte) length;

    return csrBytes;
}

String toPEM(byte[] csrBytes) {
    String base64CSR = Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(csrBytes);
    return "-----BEGIN CERTIFICATE REQUEST-----\n" + base64CSR + "\n-----END CERTIFICATE REQUEST-----";
}

生成证书

-----BEGIN CERTIFICATE REQUEST-----
cQIBADA0MAwxCgwITXlDbGllbnQwEjEQDA5NeU9yZ2FuaXphdGlvbjAIMQYMBENp
dHkwBjEEDAJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALjePfuq
/FCzB5TE9A6oq6BOGEEufqHmU0Xsn93bG8Hk5YoShnFQ4GwW9z1+NPoc+1RZOpfT
TlIxd7lHb86q5vvq6AhZ9nvtxkkQ491A3aZHJwQD2w3q8/6JH/8h7kPzGLnqsmUb
2oTfIeIcQXCdlKg2QI6BocB1I0HdrycFYTkYfVlUIgsrNx+8Vu09QsX1BahqGs/C
Jkl+Xs30nwMBC3buVYW5Nk5fM5z/fDQW+nJBCtJhXFiu4czedCx0YcPaY/W0tkj3
4SELBjr7aaWORy1Wa9jeLMyc63s8JCst7vl+/otEKoBL8sRVrOozlz+rWwalCWbs
QiF9pc/JeCYSEgMCAwEAATANBgkqhkiG9w0BAQsFAAMBAKfAVIwcl6rWYHKo/KYq
ikFjoyjxMRL6NuB0K85TaNeJTUyPL6gD2qkbepEbIzpq0useQTLgXPq4XGkiVK+0
Ey/E+wzStiYcJXNIu/CxTodz+Jnc7KNIus6GbaVNybh4hUOK1MVPmsuXv1eJuus7
xiaTEgMkEYd9K5yfOU6ZEFaRnpT89VJQrD92AaC7GhIR6n0ZM5z7YG5h3upNah5a
vlEG5alWd/hCxouML3TaOKaq5UjVN51FoOuNCjUHzJr6hzZc3nUPwS9slE8FZyHg
5kwTxIAJ84WdDrG+m0cTG+MNnqmazkzeHNqf6QEH/xecQd6MDliftvXUJe8H2N0O
LA0=
-----END CERTIFICATE REQUEST-----

当我跑步时:

openssl req -in request.csr -noout -text
然后我得到以下错误:

C08F6E51F87F0000:error:068000A8:asn1 encoding routines:asn1_check_tlen:wrong tag:crypto/asn1/tasn_dec.c:1194:
C08F6E51F87F0000:error:0688010A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:349:Type=X509_REQ
error: unable to load X509 request from file 'request.csr'

我的密钥对生成代码 -

public static KeyPair generateRSAKeyPairWithAuth(String alias) throws Exception {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
    keyGen.initialize(new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
            .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationValidityDurationSeconds(120)
            .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, KeyProperties.SIGNATURE_PADDING_RSA_PSS)
            .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512).build());
    return keyGen.generateKeyPair();
}

注意:我不想为此使用 BouncyCastle 或 SpongyCastle。

java android certificate asn.1 csr
1个回答
0
投票

我注意到的第一件事:您没有保留空间来编码外部序列的长度,因此语句

csrBytes[startPos - 1] = (byte) length
实际上覆盖了您放在开头的 0x30 标记。解决这个问题有点棘手,因为您不知道需要保留多少字节来编码长度,但您可以根据测试数据运行它。

主题、公钥和算法标识符的编码看起来没问题,但签名的编码不行,看起来像signature.length返回0,因为长度在数据中编码为1。当您在此之后写入签名时,它会被解释为 ASN.1 TLV,当然这会终止任何进一步的解码。

下面是我的 ASN.1 工具的输出,尝试解码您生成的内容,说明了上述几点:

  1. 偏移量 0x00 处的标签不正确
  2. 在偏移量 0x16f 处使用长度 1 进行编码
  3. 无法解码从偏移量 0x172 开始的签名
0x00000000:|#>[APPLICATION 17](0x02)
0x00000002:   |->[UNIVERSAL 1](0x00) = ""
0x00000004:|#>[UNIVERSAL 16](0x34)
0x00000006:   |#>[UNIVERSAL 16](0x0c)
0x00000008:      |#>[UNIVERSAL 17](0x0a)
0x0000000a:         |->[UNIVERSAL 12](0x08) = "MyClient"
0x00000014:   |#>[UNIVERSAL 16](0x12)
0x00000016:      |#>[UNIVERSAL 17](0x10)
0x00000018:         |->[UNIVERSAL 12](0x0e) = "MyOrganization"
0x00000028:   |#>[UNIVERSAL 16](0x08)
0x0000002a:      |#>[UNIVERSAL 17](0x06)
0x0000002c:         |->[UNIVERSAL 12](0x04) = "City"
0x00000032:   |#>[UNIVERSAL 16](0x06)
0x00000034:      |#>[UNIVERSAL 17](0x04)
0x00000036:         |->[UNIVERSAL 12](0x02) = "US"
0x0000003a:|#>[UNIVERSAL 16](0x0122)
0x0000003e:   |#>[UNIVERSAL 16](0x0d)
0x00000040:      |->[UNIVERSAL 6](0x09) = "1.2.840.113549.1.1.1"
0x0000004b:      |->[UNIVERSAL 5](0x00) = ""
0x0000004d:   |->[UNIVERSAL 3](0x010f) = "001100001000001000000001000010100000001010000010000000010000000100000000101110001101111000111101111110111010101011111100010100001011001100000111100101001100010011110100000011101010100010101011101000000100111000011000010000010010111001111110101000011110011001010011010001011110110010011111110111011101101100011011110000011110010011100101100010100001001010000110011100010101000011100000011011000001011011110111001111010111111000110100111110100001110011111011010101000101100100111010100101111101001101001110010100100011000101110111101110010100011101101111110011101010101011100110111110111110101011101000000010000101100111110110011110111110110111000110010010010001000011100011110111010100000011011101101001100100011100100111000001000000001111011011000011011110101011110011111111101000100100011111111111110010000111101110010000111111001100011000101110011110101010110010011001010001101111011010100001001101111100100001111000100001110001000001011100001001110110010100101010000011011001000000100011101000000110100001110000000111010100100011010000011101110110101111001001110000010101100001001110010001100001111101010110010101010000100010000010110010101100110111000111111011110001010110111011010011110101000010110001011111010100000101101010000110101000011010110011111100001000100110010010010111111001011110110011011111010010011111000000110000000100001011011101101110111001010101100001011011100100110110010011100101111100110011100111001111111101111100001101000001011011111010011100100100000100001010110100100110000101011100010110001010111011100001110011001101111001110100001011000111010001100001110000111101101001100011111101011011010010110110010010001111011111100001001000010000101100000110001110101111101101101001101001011000111001000111001011010101011001101011110110001101111000101100110011001001110011101011011110110011110000100100001010110010110111101110111110010111111011111110100010110100010000101010100000000100101111110010110001000101010110101100111010100011001110010111001111111010101101011011000001101010010100001001011001101110110001000010001000010111110110100101110011111100100101111000001001100001001000010010000000110000001000000011000000010000000000000001"
0x00000160:|#>[UNIVERSAL 16](0x0d)
0x00000162:   |->[UNIVERSAL 6](0x09) = "1.2.840.113549.1.1.11"
0x0000016d:   |->[UNIVERSAL 5](0x00) = ""
0x0000016f:|->[UNIVERSAL 3](0x01) = ""
Error while reading the datafile.
Data error at offset 436 (0x000001b4)
The length read for an encoding is greater than 18446744073709551615,
which is the maximum allowed on this platform.

File Offset   Octets indicating the length
-----------   ----------------------------
 0x00000173   0x548c1c97aad66072a8fca62a8a4163a328f13112fa36e0742bce5368d7894d4c8f2fa803daa91b7a911b233a6ad2eb1e4132e05cfab85c692254afb4132fc4fb
© www.soinside.com 2019 - 2024. All rights reserved.