我正在尝试在 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。
我注意到的第一件事:您没有保留空间来编码外部序列的长度,因此语句
csrBytes[startPos - 1] = (byte) length
实际上覆盖了您放在开头的 0x30 标记。解决这个问题有点棘手,因为您不知道需要保留多少字节来编码长度,但您可以根据测试数据运行它。
主题、公钥和算法标识符的编码看起来没问题,但签名的编码不行,看起来像signature.length返回0,因为长度在数据中编码为1。当您在此之后写入签名时,它会被解释为 ASN.1 TLV,当然这会终止任何进一步的解码。
下面是我的 ASN.1 工具的输出,尝试解码您生成的内容,说明了上述几点:
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