You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:14:56