簡體   English   中英

在Java中加載原始的64字節長的ECDSA公鑰

[英]Loading raw 64-byte long ECDSA public key in Java

我有原始(r,s)格式的ECDSA NIST P-256公鑰。 似乎沒有簡單的方法可以將其加載到實現java.security.interfaces.ECPublicKey的對象中。

加載64字節公共密鑰以便可以用來檢查簽名的最干凈的方法是什么?

EC功能需要Java 7,而Base 64編碼器/解碼器則需要Java 8,沒有其他庫-僅是純Java。 請注意,在打印輸出時,這實際上會將公鑰顯示為已命名的曲線 ,而大多數其他解決方案則不會這樣做。 如果您具有最新的運行時, 則其他答案更為簡潔。

如果我們使用ECPublicKeySpec做到這一點,將很難做到這ECPublicKeySpec 因此,讓我們作弊一下,改用X509EncodedKeySpec

private static byte[] P256_HEAD = Base64.getDecoder().decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");

/**
 * Converts an uncompressed secp256r1 / P-256 public point to the EC public key it is representing.
 * @param w a 64 byte uncompressed EC point consisting of just a 256-bit X and Y
 * @return an <code>ECPublicKey</code> that the point represents 
 */
public static ECPublicKey generateP256PublicKeyFromFlatW(byte[] w) throws InvalidKeySpecException {
    byte[] encodedKey = new byte[P256_HEAD.length + w.length];
    System.arraycopy(P256_HEAD, 0, encodedKey, 0, P256_HEAD.length);
    System.arraycopy(w, 0, encodedKey, P256_HEAD.length, w.length);
    KeyFactory eckf;
    try {
        eckf = KeyFactory.getInstance("EC");
    } catch (NoSuchAlgorithmException e) {
        throw new IllegalStateException("EC key factory not present in runtime");
    }
    X509EncodedKeySpec ecpks = new X509EncodedKeySpec(encodedKey);
    return (ECPublicKey) eckf.generatePublic(ecpks);
}

用法:

ECPublicKey key = generateP256PublicKeyFromFlatW(w);
System.out.println(key);

其背后的想法是創建一個臨時的X509編碼密鑰,該密鑰愉快地以結尾的公共點w結尾。 之前的字節包含命名曲線的OID的ASN.1 DER編碼和結構開銷,最后一個字節04指示未壓縮的點。 這是一個結構的示例 ,對32字節的X和Y使用值1和2。

刪除未壓縮點值的32字節X和Y值以創建標頭。 這僅適用於該點,因為該點的大小是靜態的-它在末端的位置僅由曲線的大小確定。

現在,函數generateP256PublicKeyFromFlatW所需的全部就是將接收到的公共點w添加到標頭,並通過為X509EncodedKeySpec實現的解碼器運行它。


上面的代碼使用了原始的,未壓縮的公共EC點-僅有32個字節的X和Y-沒有值為04的未壓縮點指示符。 當然,也很容易支持65個字節的壓縮點:

/**
 * Converts an uncompressed secp256r1 / P-256 public point to the EC public key it is representing.
 * @param w a 64 byte uncompressed EC point starting with <code>04</code>
 * @return an <code>ECPublicKey</code> that the point represents 
 */
public static ECPublicKey generateP256PublicKeyFromUncompressedW(byte[] w) throws InvalidKeySpecException {
    if (w[0] != 0x04) {
        throw new InvalidKeySpecException("w is not an uncompressed key");
    }
    return generateP256PublicKeyFromFlatW(Arrays.copyOfRange(w, 1, w.length));
}

最后,我使用以下代碼在基數64中生成了常量P256_HEAD頭值:

private static byte[] createHeadForNamedCurve(String name, int size)
        throws NoSuchAlgorithmException,
        InvalidAlgorithmParameterException, IOException {
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
    ECGenParameterSpec m = new ECGenParameterSpec(name);
    kpg.initialize(m);
    KeyPair kp = kpg.generateKeyPair();
    byte[] encoded = kp.getPublic().getEncoded();
    return Arrays.copyOf(encoded, encoded.length - 2 * (size / Byte.SIZE));
}

致電者:

String name = "NIST P-256";
int size = 256;
byte[] head = createHeadForNamedCurve(name, size);
System.out.println(Base64.getEncoder().encodeToString(head));

Java確實使加密技術走了很長的路。

從給定的EC點創建公鑰的過程:

  1. 根據給定的坐標構造ECPoint對象。
  2. 根據曲線信息構造ECParameterSpec對象。
  3. 構造一個ECPublicKeySpec從你的對象ECPoint和你ECParameterSpec對象。
  4. 使用ECPublicKeySpec對象調用KeyFactory.generatePublic()以檢索PublicKey對象。
  5. 根據需要將PublicKey轉換為ECPublicKey

下面的例子:

// Setup for P-256 curve params

BigInteger p256_p = new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);

BigInteger p256_a = new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16);
BigInteger p256_b = new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
byte[] p256_seed = {
                        (byte) 0xc4, (byte) 0x9d, (byte) 0x36, (byte) 0x08, 
                        (byte) 0x86, (byte) 0xe7, (byte) 0x04, (byte) 0x93, 
                        (byte) 0x6a, (byte) 0x66, (byte) 0x78, (byte) 0xe1, 
                        (byte) 0x13, (byte) 0x9d, (byte) 0x26, (byte) 0xb7, 
                        (byte) 0x81, (byte) 0x9f, (byte) 0x7e, (byte) 0x90
                    };

BigInteger p256_xg = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16);
BigInteger p256_yg = new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16);

BigInteger p256_n = new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);

// Construct prime field
ECFieldFp p256_field = new ECFieldFp(p256_p);

// Construct curve from parameters
EllipticCurve p256 = new EllipticCurve(p256_field, p256_a, p256_b, p256_seed);

// Construct base point for curve
ECPoint p256_base = new ECPoint(p256_xg, p256_yg);

// Construct curve parameter specifications object
ECParameterSpec p256spec = new ECParameterSpec(p256, p256_base, p256_n, 1); // Co-factor 1 for prime curves

// ------------------------------------------------------------- //

// Construct EC point from "raw" public key
ECPoint point = new ECPoint(r, s); // r, s is of type BigInteger

// Create a EC public key specification object from point and curve
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, p256spec);

// Retrieve EC KeyFactory
KeyFactory ECFactory = KeyFactory.getInstance("EC");

// Generate public key via KeyFactory
PublicKey pubKey = ECFactory.generatePublic(pubKeySpec);
ECPublicKey ECPubKey = (ECPublicKey) pubKey;

出於性能原因,一次生成ECParameterSpec(可能在靜態初始化程序塊中)可能會有所幫助。

注意:生成ECParameterSpec對象的方法可能更簡單(例如,通過命名曲線),但是到目前為止,我僅發現ECGenParameterSpec具有此功能。 請在評論中讓我知道是否有較不痛苦的方法。


為了避免上述麻煩,請在X.509下對EC密鑰進行編碼,這將完全描述密鑰使其加載變得更加容易。

在Java中,使用ECPublicKey,您需要做的就是調用ECPublicKey.getEncoded()並將字節數組傳遞/保存到接下來需要密鑰的位置。 然后可以通過以下方式重建X.509編碼的密鑰:

// Retrieve EC KeyFactory
KeyFactory ECFactory = KeyFactory.getInstance("EC");

// Generate public key via KeyFactory
PublicKey pubKey = ECFactory.generatePublic(new X509EncodedKeySpec(data));
ECPublicKey ECPubKey = (ECPublicKey) pubKey;

其中“數據”是編碼的字節數組。

EC公鑰是由x和y坐標組成的點。 我曾經編寫以下代碼段將EC x,y點轉換為publicKey對象。 希望這會幫助你。 供你參考:

rawPubKey = 04 + x坐標+ y坐標(十六進制字符串)

curveName = P-256(字符串)

P-256的示例EC公鑰點:

rawPubKey = 04 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5

BC提供者:您需要Bouncy Castle提供者。 我使用了bcprov-jdk15on-149.jar ,但是您可以從此處下載最新版本。

/**
 * This method converts the uncompressed raw EC public key into java.security.interfaces.ECPublicKey
 * @param rawPubKey 
 * @param curveName
 * @return java.security.interfaces.ECPublicKey
 */
public ECPublicKey ucPublicKeyToPublicKey(String rawPubKey, String curveName) {
    byte[] rawPublicKey = Helper.toByte(rawPubKey); 
    ECPublicKey ecPublicKey = null;
    KeyFactory kf = null;

    ECNamedCurveParameterSpec ecNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec(curveName);
    ECCurve curve = ecNamedCurveParameterSpec.getCurve();
    EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, ecNamedCurveParameterSpec.getSeed());
    java.security.spec.ECPoint ecPoint = ECPointUtil.decodePoint(ellipticCurve, rawPublicKey);
    ECParameterSpec ecParameterSpec = EC5Util.convertSpec(ellipticCurve, ecNamedCurveParameterSpec);
    java.security.spec.ECPublicKeySpec publicKeySpec = new java.security.spec.ECPublicKeySpec(ecPoint, ecParameterSpec);

    kf = java.security.KeyFactory.getInstance("EC");

    try {
        ecPublicKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
    } catch (Exception e) {
        System.out.println("Caught Exception public key: " + e.toString());
    }

    return ecPublicKey;
}

編輯:這是toByte()方法:

public static byte[] toByte(String hex) {
        if (hex == null)
            return null;
        hex = hex.replaceAll("\\s", "");
        byte[] buffer = null;
        if (hex.length() % 2 != 0) {
            hex = "0" + hex;
        }
        int len = hex.length() / 2;
        buffer = new byte[len];
        for (int i = 0; i < len; i++) {
            buffer[i] = (byte) Integer.parseInt(
                    hex.substring(i * 2, i * 2 + 2), 16);
        }
        return buffer;
    }

但是您可以使用自己的實現。 這是另一個:

import javax.xml.bind.DatatypeConverter;
public static byte[] toByte(String hex) {{
    return DatatypeConverter.parseHexBinary(hex);
}

在Bouncycastle的幫助下,這對我有用:

ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint publicPoint =  ECPointUtil.decodePoint(params.getCurve(), publicKeyByteArray);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, params);
PublicKey publicKey =  keyFactory.generatePublic(pubKeySpec);

加載64字節公共密鑰以便可以用來檢查簽名的最干凈的方法是什么?

我可以召集的最干凈的! 也應該與其他曲線一起使用。

注意:僅限於SunJCE提供程序或Android API 26+(可能有更多提供此功能的提供程序,目前我尚未意識到。

public static ECPublicKey rawToEncodedECPublicKey(String curveName, byte[] rawBytes) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidParameterSpecException {
    KeyFactory kf = KeyFactory.getInstance("EC");
    byte[] x = Arrays.copyOfRange(rawBytes, 0, rawBytes.length/2);
    byte[] y = Arrays.copyOfRange(rawBytes, rawBytes.length/2, rawBytes.length);
    ECPoint w = new ECPoint(new BigInteger(1,x), new BigInteger(1,y));
    return (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(w, ecParameterSpecForCurve(curveName)));
}

public static ECParameterSpec ecParameterSpecForCurve(String curveName) throws NoSuchAlgorithmException, InvalidParameterSpecException {
    AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
    params.init(new ECGenParameterSpec(curveName));
    return params.getParameterSpec(ECParameterSpec.class);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM