java.security.PublicKey#getEncoded()
返回密钥的X509表示,在ECC情况下,与原始ECC值相比会增加很多开销。
我希望能够在大多数紧凑的表示中将PublicKey转换为字节数组(反之亦然)(即尽可能小字节块)。
KeyType(ECC)和具体曲线类型是事先已知的,因此不需要对它们的信息进行编码。
解决方案可以使用Java API,BouncyCastle或任何其他自定义代码/库(只要许可证不需要使用开源专有代码)。
答案 0 :(得分:8)
这个功能也存在于Bouncy Castle中,但我将展示如何使用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");
}
}
}
}
答案 1 :(得分:1)
尝试在java中生成一个未压缩的表示形式几乎杀了我!希望我早些时候能找到这个(尤其是Maarten Bodewes&#39;优秀答案)。我想在答案中指出一个问题并提供改进:
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");
}
这个丑陋的位是必要的,因为BigInteger
吐出字节数组表示的方式:&#34; 数组将包含表示此BigInteger
所需的最小字节数,包括至少一个符号位&#34; (toByteArray javadoc)。这意味着a。)如果设置了x
或y
的最高位,则0x00
将被添加到数组中,并且b。)将0x00
&#39; s将被修剪。第一个分支处理修剪后的0x00
&#39;以及第二个分支处理前置0x00
。
&#34>修剪前导零&#34;&#34;导致代码中的问题确定x
和y
的预期长度:
int keySizeBytes = (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1)
/ Byte.SIZE;
如果曲线的order
有一个前导0x00
,它会被截断,bitLength
不会考虑它。生成的密钥长度太短。获得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
答案 2 :(得分:1)
以下是我用于解压缩公钥的BouncyCastle方法:
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;
}
答案 3 :(得分:0)
使用BouncyCastle,ECPoint.getEncoded(true)
返回该点的压缩表示。基本上仿射X坐标与仿射Y的符号位。
答案 4 :(得分:0)
2021 年只需使用 Tink 库
public static byte[] pointEncode(EllipticCurves.CurveType curveType,
EllipticCurves.PointFormatType format,
ECPoint point)
throws GeneralSecurityException