package org.uic.barcode.utils; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.KeyFactory; import java.security.Provider; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; public class ECKeyEncoder { private static final byte X962_UNCOMPRESSED_POINT_INDICATOR = 0x04; private static final byte X962_ODD = 0x02; private static final byte X962_EVEN = 0x03; public static final String ENCODING_X509 = "X509"; public static final String ENCODING_X962_UNCOMPESSED = "X962_UNCOMPRESSED"; public static final String ENCODING_X962_COMPRESSED = "X962_COMPRESSED"; public static PublicKey fromEncoded (byte[] keyBytes, String oid, Provider provider) { PublicKey key = null; String keyAlgName = null; try { keyAlgName = AlgorithmNameResolver.getName(AlgorithmNameResolver.TYPE_KEY_GENERATOR_ALG, oid,provider); } catch (Exception e1) { throw new IllegalArgumentException("algorithm unknown in: " + provider.getName()); } if (keyAlgName == null || keyAlgName.length() == 0) { throw new IllegalArgumentException("algorithm unknown in: " + provider.getName()); } //try standard X.509 encoding first try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); if (keyAlgName != null && keyAlgName.length() > 0) { KeyFactory keyFactory = KeyFactory.getInstance(keyAlgName,provider); key = keyFactory.generatePublic(keySpec); } } catch (Exception e) { //try next option } if (key != null) return key; if (keyBytes[0] == X962_UNCOMPRESSED_POINT_INDICATOR) { } //maybe a compressed X9.62 eliptic key if (keyBytes[0] == X962_ODD || keyBytes[0] == X962_EVEN) { try { //we need to know the curve! String curveName = EllipticCurveNames.getInstance().getName(oid); //get the curve parameters AlgorithmParameters parameters = AlgorithmParameters.getInstance(keyAlgName, provider); parameters.init(new ECGenParameterSpec(curveName)); ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); EllipticCurve curve = ecParameters.getCurve(); //reconstruct the uncompressed version with the curve byte[] uncompressed = decompressPubkey(keyBytes, curve); //decode the uncompressed key return fromUncompressedPoint(uncompressed, ecParameters); } catch (Exception e) { key = null; // failed } } //try X962 uncompressed if (keyBytes[0] == X962_UNCOMPRESSED_POINT_INDICATOR) { try { //we need to know the curve! String curveName = EllipticCurveNames.getInstance().getName(oid); //get the curve parameters AlgorithmParameters parameters = AlgorithmParameters.getInstance(keyAlgName, provider); parameters.init(new ECGenParameterSpec(curveName)); ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); //decode the uncompressed key return fromUncompressedPoint(keyBytes, ecParameters); } catch(Exception e) { //failed } } throw new IllegalArgumentException("public key format unknown"); } public static byte[] getEncoded(PublicKey key, String encoding){ if (encoding.equals(ENCODING_X509)) { return key.getEncoded(); } else if (encoding.equals(ENCODING_X962_UNCOMPESSED)) { if (key instanceof ECPublicKey) { return toUncompressedPoint((ECPublicKey) key); } } else if (encoding.equals(ENCODING_X962_COMPRESSED)) { if (key instanceof ECPublicKey) { ECPoint point = ((ECPublicKey) key).getW(); byte[] x = toUnsignedBytes(point.getAffineX()); BigInteger y = point.getAffineY(); byte[] compressed = new byte[x.length + 1]; //compression indicator if (y.testBit(0)) { compressed[0] = 0x03; } else { compressed[0] = 0x02; } System.arraycopy(x, 0, compressed, 1, x.length); return compressed; } } throw new IllegalArgumentException("unknown encoding"); } private static ECPublicKey fromUncompressedPoint( final byte[] uncompressedPoint, final ECParameterSpec params) throws Exception { int offset = 0; if (uncompressedPoint[offset++] != X962_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); } private 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; } static final BigInteger MODULUS = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16); static final BigInteger POW = MODULUS.add(BigInteger.ONE).shiftRight(2); // Given a 33-byte compressed public key, this returns a 65-byte uncompressed key. private static byte[] decompressPubkey(byte[] compressedKey, EllipticCurve curve ) { // Check array length and type indicator byte if (compressedKey.length != 33 || compressedKey[0] != 2 && compressedKey[0] != 3) throw new IllegalArgumentException(); final byte[] xCoordBytes = Arrays.copyOfRange(compressedKey, 1, compressedKey.length); final BigInteger xCoord = new BigInteger(1, xCoordBytes); // Range [0, 2^256) BigInteger temp = xCoord.pow(2).add(curve.getA()); temp = temp.multiply(xCoord); temp = temp.add(curve.getB()); temp = temp.modPow(POW, MODULUS); //temp = sqrtMod(temp.add(curveParamB)); boolean tempIsOdd = temp.testBit(0); boolean yShouldBeOdd = compressedKey[0] == 3; if (tempIsOdd != yShouldBeOdd) temp = temp.negate().mod(MODULUS); final BigInteger yCoord = temp; // Copy the x coordinate into the new // uncompressed key, and change the type byte byte[] result = Arrays.copyOf(compressedKey, 65); result[0] = 0x04; // Carefully copy the y coordinate into uncompressed key final byte[] yCoordBytes = yCoord.toByteArray(); for (int i = 0; i < 32 && i < yCoordBytes.length; i++) result[result.length - 1 - i] = yCoordBytes[yCoordBytes.length - 1 - i]; return result; } private static byte[] toUnsignedBytes(BigInteger i) { byte[] b = i.abs().toByteArray(); //remove top sign bit if (b[0] == 0) { b = Arrays.copyOfRange(b, 1, b.length); } return b; } }