ECC公钥的Java压缩表示

ECC公钥的Java压缩表示,java,encoding,cryptography,public-key,elliptic-curve,Java,Encoding,Cryptography,Public Key,Elliptic Curve,java.security.PublicKey#getEncoded()返回密钥的X509表示形式,在ECC情况下,与原始ECC值相比会增加大量开销 我希望能够以最紧凑的表示(即尽可能小的字节块)将公钥转换为字节数组(反之亦然) 键类型(ECC)和具体曲线类型是预先知道的,因此不需要对它们的相关信息进行编码 解决方案可以使用Java API、BouncyCastle或任何其他自定义代码/库(只要许可证不意味着需要开放将使用它的源代码专有代码)。BouncyCastle中也提供了此功能,但我将演示

java.security.PublicKey#getEncoded()
返回密钥的X509表示形式,在ECC情况下,与原始ECC值相比会增加大量开销

我希望能够以最紧凑的表示(即尽可能小的字节块)将公钥转换为字节数组(反之亦然)

键类型(ECC)和具体曲线类型是预先知道的,因此不需要对它们的相关信息进行编码


解决方案可以使用Java API、BouncyCastle或任何其他自定义代码/库(只要许可证不意味着需要开放将使用它的源代码专有代码)。

BouncyCastle中也提供了此功能,但我将演示如何仅使用Java完成此过程,以防有人需要它:

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.util.Arrays;

public class Curvy {

    private static final byte UNCOMPRESSED_POINT_INDICATOR = 0x04;

    public static ECPublicKey fromUncompressedPoint(
            final byte[] uncompressedPoint, final ECParameterSpec params)
            throws Exception {

        int offset = 0;
        if (uncompressedPoint[offset++] != UNCOMPRESSED_POINT_INDICATOR) {
            throw new IllegalArgumentException(
                    "Invalid uncompressedPoint encoding, no uncompressed point indicator");
        }

        int keySizeBytes = (params.getOrder().bitLength() + Byte.SIZE - 1)
                / Byte.SIZE;

        if (uncompressedPoint.length != 1 + 2 * keySizeBytes) {
            throw new IllegalArgumentException(
                    "Invalid uncompressedPoint encoding, not the correct size");
        }

        final BigInteger x = new BigInteger(1, Arrays.copyOfRange(
                uncompressedPoint, offset, offset + keySizeBytes));
        offset += keySizeBytes;
        final BigInteger y = new BigInteger(1, Arrays.copyOfRange(
                uncompressedPoint, offset, offset + keySizeBytes));
        final ECPoint w = new ECPoint(x, y);
        final ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(w, params);
        final KeyFactory keyFactory = KeyFactory.getInstance("EC");
        return (ECPublicKey) keyFactory.generatePublic(ecPublicKeySpec);
    }

    public static byte[] toUncompressedPoint(final ECPublicKey publicKey) {

        int keySizeBytes = (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1)
                / Byte.SIZE;

        final byte[] uncompressedPoint = new byte[1 + 2 * keySizeBytes];
        int offset = 0;
        uncompressedPoint[offset++] = 0x04;

        final byte[] x = publicKey.getW().getAffineX().toByteArray();
        if (x.length <= keySizeBytes) {
            System.arraycopy(x, 0, uncompressedPoint, offset + keySizeBytes
                    - x.length, x.length);
        } else if (x.length == keySizeBytes + 1 && x[0] == 0) {
            System.arraycopy(x, 1, uncompressedPoint, offset, keySizeBytes);
        } else {
            throw new IllegalStateException("x value is too large");
        }
        offset += keySizeBytes;

        final byte[] y = publicKey.getW().getAffineY().toByteArray();
        if (y.length <= keySizeBytes) {
            System.arraycopy(y, 0, uncompressedPoint, offset + keySizeBytes
                    - y.length, y.length);
        } else if (y.length == keySizeBytes + 1 && y[0] == 0) {
            System.arraycopy(y, 1, uncompressedPoint, offset, keySizeBytes);
        } else {
            throw new IllegalStateException("y value is too large");
        }

        return uncompressedPoint;
    }

    public static void main(final String[] args) throws Exception {

        // just for testing

        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
        kpg.initialize(163);

        for (int i = 0; i < 1_000; i++) {
            final KeyPair ecKeyPair = kpg.generateKeyPair();

            final ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic();
            final ECPublicKey retrievedEcPublicKey = fromUncompressedPoint(
                    toUncompressedPoint(ecPublicKey), ecPublicKey.getParams());
            if (!Arrays.equals(retrievedEcPublicKey.getEncoded(),
                    ecPublicKey.getEncoded())) {
                throw new IllegalArgumentException("Whoops");
            }
        }
    }
}
import java.math.biginger;
导入java.security.KeyFactory;
导入java.security.KeyPair;
导入java.security.KeyPairGenerator;
导入java.security.interfaces.ECPublicKey;
导入java.security.spec.ECParameterSpec;
导入java.security.spec.ECPoint;
导入java.security.spec.ECPublicKeySpec;
导入java.util.array;
公共类曲线{
私有静态最终字节未压缩\u点\u指示器=0x04;
public static ECPublicKey from UncompressedPoint(
最终字节[]未压缩点,最终ECParameterSpec参数)
抛出异常{
整数偏移=0;
if(未压缩点[偏移量++!=未压缩点指示器){
抛出新的IllegalArgumentException(
“无效的解压缩点编码,没有解压缩点指示器”);
}
int keySizeBytes=(params.getOrder().bitLength()+Byte.SIZE-1)
/字节大小;
if(解压点.length!=1+2*键大小字节){
抛出新的IllegalArgumentException(
“无效的解压缩点编码,大小不正确”);
}
final BigInteger x=新的BigInteger(1,Arrays.copyOfRange(
解压点、偏移量、偏移量+键大小字节);
偏移量+=键大小字节;
final BigInteger y=新的BigInteger(1,Arrays.copyOfRange(
解压点、偏移量、偏移量+键大小字节);
最终ECW点=新ECW点(x,y);
最终ECPublicKeySpec ECPublicKeySpec=新ECPublicKeySpec(w,参数);
final KeyFactory KeyFactory=KeyFactory.getInstance(“EC”);
return(ECPublicKey)keyFactory.generatePublic(ecPublicKeySpec);
}
公共静态字节[]到压缩点(最终ECPublicKey公钥){
int keySizeBytes=(publicKey.getParams().getOrder().bitLength()+Byte.SIZE-1)
/字节大小;
最终字节[]未压缩点=新字节[1+2*键大小字节];
整数偏移=0;
解压点[offset++]=0x04;
最后一个字节[]x=publicKey.getW().getAffineX().toByteArray();

如果(x.length试图用java生成一个未压缩的表示几乎要了我的命!希望我能早点发现这一点(特别是Maarten Bodewes的优秀答案)。我想指出答案中的一个问题,并提供一个改进:

if (x.length <= keySizeBytes) {
        System.arraycopy(x, 0, uncompressedPoint, offset + keySizeBytes
                - x.length, x.length);
    } else if (x.length == keySizeBytes + 1 && x[0] == 0) {
        System.arraycopy(x, 1, uncompressedPoint, offset, keySizeBytes);
    } else {
        throw new IllegalStateException("x value is too large");
    }
如果曲线的
顺序
有一个前导的
0x00
,它会被截断,并且不会被
位长度
考虑。生成的键长度太短。获得
p
位长度的令人难以置信的卷积(但正确的)方法是:

int keySizeBits = publicKey.getParams().getCurve().getField().getFieldSize();
int keySizeBytes = (keySizeBits + 7) >>> 3;
+7
用于补偿不是2的幂的位长度。)

此问题影响标准JCA(
X9_62_c2tnb431r1
)交付的至少一条曲线,该曲线具有前导零的顺序:

000340340340340 34034034034034034
034034034034034 0340340340323c313
fab50589703b5ec 68d3587fec60d161c
c149c1ad4a91
以下是我用来解包公钥的方法:

public static byte[] extractData(final @NonNull PublicKey publicKey) {
    final SubjectPublicKeyInfo subjectPublicKeyInfo =
            SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
    final byte[] encodedBytes = subjectPublicKeyInfo.getPublicKeyData().getBytes();
    final byte[] publicKeyData = new byte[encodedBytes.length - 1];

    System.arraycopy(encodedBytes, 1, publicKeyData, 0, encodedBytes.length - 1);

    return publicKeyData;
}

使用BouncyCastle,
ECPoint.getEncoded(true)
返回点的压缩表示。基本上,仿射X坐标和仿射Y的符号位。

在2021年,只需使用该库即可


我使用了标准的未压缩点表示法。这意味着指示符字节当然是开销。此外,也有压缩点表示法,但压缩点存在一些IP问题,我认为Bouncy对点压缩有一些支持。未压缩点是可以的,一些公司拥有与点相关的专利压缩。您已经提到功能存在于BC本身,您是否可以添加关于如何使用BC API进行压缩的示例?@Daimon您可以自己看看吗?我目前不使用压缩点,所以我基本上必须自己编写,而且时间很短。很抱歉误解。我不是在问点压缩but使用BC API进行公钥字节转换:)如果我知道怎么做,我不会在allOK上问主要问题,但你看了有多难?它不像
ECPoint.getEncoded()
ECPoint.Fp.getEncoded(布尔压缩)
那么难找到。
public static byte[] extractData(final @NonNull PublicKey publicKey) {
    final SubjectPublicKeyInfo subjectPublicKeyInfo =
            SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
    final byte[] encodedBytes = subjectPublicKeyInfo.getPublicKeyData().getBytes();
    final byte[] publicKeyData = new byte[encodedBytes.length - 1];

    System.arraycopy(encodedBytes, 1, publicKeyData, 0, encodedBytes.length - 1);

    return publicKeyData;
}
public static byte[] pointEncode(EllipticCurves.CurveType curveType,
                                 EllipticCurves.PointFormatType format,
                                 ECPoint point)
                          throws GeneralSecurityException