Java生成EC私钥可在Chrome导入,Safari导入失败求助
问题:Java生成的EC私钥在Safari中无法导入,Chrome正常
我用Java生成P-384(secp384r1)椭圆曲线私钥,通过JavaScript在浏览器导入时,Chrome能成功导入,但Safari报错:DATA PROVIDED TO AN OPERATION DOES NOT MEET REQUIREMENTS。
原代码
JavaScript导入代码
<!DOCTYPE html> <html> <head> <title>ECDH Key Pair Generation</title> </head> <body> <script> //Utils function _extractRawKeyMaterial(pem, type) { const pemHeader = `-----BEGIN ${type} KEY-----`; const pemFooter = `-----END ${type} KEY-----`; const endingIndex = pem.indexOf(pemFooter); const startingIndex = pem.indexOf(pemHeader) + pemHeader.length; const pemContents = pem.substring(startingIndex, endingIndex); var return_object = convertBase64StringToArrayBuffer(pemContents.trim()); return return_object; } const convertBase64StringToArrayBuffer = base64String => { const text = window.atob(base64String); return convertStringToArrayBuffer(text); }; const convertStringToArrayBuffer = str => { const buf = new ArrayBuffer(str.length); const bufView = new Uint8Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }; // private key var privateKeyGenerated = `-----BEGIN PRIVATE KEY----- ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDAMvyd7HU0FwJxgs5N87NVw MPOR60umJXnhPjdtn0O0RHgx2J0sVnvw7B6ue1Wb5uQ= -----END PRIVATE KEY-----` // Pass the loaded private key to your function _loadEccPrivateKey(privateKeyGenerated); // Code working in chrome but fails in safari with an error : Data provided to an operation does not meet requirements async function _loadEccPrivateKey(pemKey) { try { const rawKey = _extractRawKeyMaterial(pemKey.trim(), "PRIVATE"); const key = await window.crypto.subtle.importKey( "pkcs8", // Format for private keys rawKey, { name: "ECDH", namedCurve: "P-384", }, true, ["deriveBits", "deriveKey"] // Key usages ); console.log('Imported Private Key:', key); return key; } catch (e) { console.error('Error importing private key:', e); throw e; } } </script> </body> </html>
Java生成代码
import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.FileOutputStream; import java.io.IOException; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.util.Base64; public class TestApplication { private static final String CURVE = "secp384r1"; // P-384 curve public static void main(String[] args) { try { // Add BouncyCastle Provider Security.addProvider(new BouncyCastleProvider()); // Generate EC key pair ECGenParameterSpec parameterSpec = new ECGenParameterSpec(CURVE); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); keyPairGenerator.initialize(parameterSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Extract and print private key PrivateKey privateKey = keyPair.getPrivate(); String privateKeyPem = convertToPem(privateKey); System.out.println("Private Key in PEM format:\n" + privateKeyPem); // Save the private key in binary format to a file (optional) String privateKeyFilePath = "private_key.bin"; saveKeyToBinaryFile(privateKey, privateKeyFilePath); } catch (Exception e) { e.printStackTrace(); } } // Convert private key to PEM format private static String convertToPem(PrivateKey privateKey) { String base64Key = Base64.getEncoder().encodeToString(privateKey.getEncoded()); return "-----BEGIN PRIVATE KEY-----\n" + base64Key + "\n-----END PRIVATE KEY-----"; } // Save the private key in binary format private static void saveKeyToBinaryFile(PrivateKey privateKey, String filePath) { try (FileOutputStream fos = new FileOutputStream(filePath)) { fos.write(privateKey.getEncoded()); } catch (IOException e) { e.printStackTrace(); } } }
问题原因
BouncyCastle生成的PKCS#8格式私钥,在Safari的Web Crypto API中存在兼容性问题:Safari对PKCS#8的ASN.1结构要求更严格,BouncyCastle默认生成的私钥包含了显式EC参数(完整曲线参数),而Safari更适配隐式EC参数(仅通过OID引用曲线)的编码格式。
解决方案
修改Java代码,移除BouncyCastle依赖,使用JCE默认提供者生成兼容的PKCS#8私钥,并规范PEM格式的换行:
修改后的Java代码
import java.io.FileOutputStream; import java.io.IOException; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; public class TestApplication { private static final String CURVE = "secp384r1"; // P-384 curve public static void main(String[] args) { try { // 使用JCE默认提供者,避免BouncyCastle的兼容性问题 ECGenParameterSpec parameterSpec = new ECGenParameterSpec(CURVE); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); keyPairGenerator.initialize(parameterSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 重新编码私钥,确保使用隐式EC参数格式 PrivateKey privateKey = keyPair.getPrivate(); PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); KeyFactory keyFactory = KeyFactory.getInstance("EC"); PrivateKey compatiblePrivateKey = keyFactory.generatePrivate(pkcs8Spec); String privateKeyPem = convertToPem(compatiblePrivateKey); System.out.println("Private Key in PEM format:\n" + privateKeyPem); String privateKeyFilePath = "private_key.bin"; saveKeyToBinaryFile(compatiblePrivateKey, privateKeyFilePath); } catch (Exception e) { e.printStackTrace(); } } // 按PEM标准格式化私钥,每行64字符 private static String convertToPem(PrivateKey privateKey) { String base64Key = Base64.getEncoder().encodeToString(privateKey.getEncoded()); StringBuilder pemBuilder = new StringBuilder("-----BEGIN PRIVATE KEY-----\n"); for (int i = 0; i < base64Key.length(); i += 64) { pemBuilder.append(base64Key.substring(i, Math.min(i + 64, base64Key.length()))).append("\n"); } pemBuilder.append("-----END PRIVATE KEY-----"); return pemBuilder.toString(); } private static void saveKeyToBinaryFile(PrivateKey privateKey, String filePath) { try (FileOutputStream fos = new FileOutputStream(filePath)) { fos.write(privateKey.getEncoded()); } catch (IOException e) { e.printStackTrace(); } } }
优化后的JavaScript解析代码(可选)
优化PEM解析逻辑,兼容各种空白字符情况:
function _extractRawKeyMaterial(pem, type) { const pemHeader = `-----BEGIN ${type} KEY-----`; const pemFooter = `-----END ${type} KEY-----`; // 移除所有空白字符,避免换行/空格导致的解析错误 const cleanPem = pem.replace(/\s/g, ''); const start = cleanPem.indexOf(pemHeader.replace(/\s/g, '')) + pemHeader.length; const end = cleanPem.indexOf(pemFooter.replace(/\s/g, '')); const pemContents = cleanPem.substring(start, end); return convertBase64StringToArrayBuffer(pemContents); } const convertBase64StringToArrayBuffer = base64String => { const text = window.atob(base64String); const buf = new ArrayBuffer(text.length); const bufView = new Uint8Array(buf); for (let i = 0; i < text.length; i++) { bufView[i] = text.charCodeAt(i); } return buf; };
验证结果
修改后生成的私钥可同时在Chrome和Safari中成功导入,正常执行ECDH密钥派生操作。
内容的提问来源于stack exchange,提问作者Chetan Tailor
相关产品推荐
相关产品推荐

