13

raw (r,s) 形式の ECDSA NIST P-256 公開鍵を持っています。java.security.interfaces.ECPublicKey を実装するオブジェクトにロードする簡単な方法はないようです。

署名のチェックに使用できるように 64 バイトの公開鍵をロードする最もクリーンな方法は何ですか?

4

5 に答える 5

15

EC 機能には Java 7 が必要であり、Base 64 エンコーダー/デコーダーには Java 8 が必要です。追加のライブラリはなく、単なる Java です。これにより、公開鍵が実際に印刷されたときに名前付き曲線として表示されることに注意してください。これは、他のほとんどのソリューションでは実行されません。最新のランタイムがある場合、この他の答えはよりクリーンです。

を使用してこれを行うと、この答えは難しくなりますECPublicKeySpecX509EncodedKeySpecそれでは、少しごまかして代わりに使用しましょう。

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 でエンコードされた一時的なキーを作成することです。これは最後に public ポイント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));
}

最後に、P256_HEAD以下を使用して base 64 で一定の​​ 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));
于 2015-05-27T02:03:13.213 に答える
6

署名のチェックに使用できるように 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);
}
于 2019-05-16T14:21:47.123 に答える
3

EC 公開鍵は、x 座標と y 座標で構成される点です。publicKeyEC x, y ポイントをオブジェクトに変換するために、次のコード セグメントを一度書きました。これがあなたを助けることを願っています。ご参考までに:

rawPubKey = 04 + x 座標 + y 座標 (16 進文字列)

曲線名 = 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);
}
于 2015-05-26T09:56:38.123 に答える