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.ECFieldFp; 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; // TODO: Auto-generated Javadoc /** * The Class ECKeyEncoder. */ public class ECKeyEncoder { /** The Constant X962_UNCOMPRESSED_POINT_INDICATOR. */ private static final byte X962_UNCOMPRESSED_POINT_INDICATOR = 0x04; /** The Constant X962_ODD. */ private static final byte X962_ODD = 0x02; /** The Constant X962_EVEN. */ private static final byte X962_EVEN = 0x03; /** The Constant ENCODING_X509. */ public static final String ENCODING_X509 = "X509"; /** The Constant ENCODING_X962_UNCOMPESSED. */ public static final String ENCODING_X962_UNCOMPESSED = "X962_UNCOMPRESSED"; /** The Constant ENCODING_X962_COMPRESSED. */ public static final String ENCODING_X962_COMPRESSED = "X962_COMPRESSED"; /** * From encoded. * * @param keyBytes the encoded key * @param oid the algorithm OID of the key algorithm * @param provider the provider of the security implementation * @return the public key */ 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; //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); //reconstruct the uncompressed version with the curve byte[] uncompressed = decompressPubkey(keyBytes, ecParameters); //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"); } /** * Gets the encoded. * * @param key the public key * @param encoding the encoding ("X509","X962_UNCOMPRESSED","X962_COMPRESSED") * @return the encoded key */ 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(); int fieldSize = (((ECPublicKey)key).getParams().getCurve().getField().getFieldSize() + 7) / 8; int keySizeBytes = fieldSize + 1; final byte[] compressed = new byte[keySizeBytes]; byte[] x = toUnsignedBytes(point.getAffineX()); BigInteger y = point.getAffineY(); //compression indicator if (y.testBit(0)) { compressed[0] = 0x03; } else { compressed[0] = 0x02; } System.arraycopy(x, 0, compressed, 1 + fieldSize - x.length, x.length); return compressed; } } throw new IllegalArgumentException("unknown encoding"); } /** * From uncompressed point. * * @param encoded the public key in uncompressed encoding * @param params the elliptic curve parameters * @return the EC public key * @throws Exception the exception */ private static ECPublicKey fromUncompressedPoint( final byte[] encoded, final ECParameterSpec params) throws Exception { int offset = 0; if (encoded[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 (encoded.length != 1 + 2 * keySizeBytes) { throw new IllegalArgumentException("Invalid uncompressedPoint encoding, not the correct size"); } final BigInteger x = new BigInteger(1, Arrays.copyOfRange(encoded, offset, offset + keySizeBytes)); offset += keySizeBytes; final BigInteger y = new BigInteger(1, Arrays.copyOfRange(encoded, 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); } /** * To uncompressed point. * * @param publicKey the public key * @return the encoded public key */ private static byte[] toUncompressedPoint(final ECPublicKey publicKey) { final byte[] xCoordBytes = toUnsignedBytes(publicKey.getW().getAffineX()); final byte[] yCoordBytes = toUnsignedBytes(publicKey.getW().getAffineY()); int fieldSize = (publicKey.getParams().getCurve().getField().getFieldSize() + 7) / 8; int keySizeBytes = 2 * fieldSize + 1; final byte[] uncompressedPoint = new byte[keySizeBytes]; System.arraycopy(xCoordBytes, 0, uncompressedPoint, 1 + fieldSize - xCoordBytes.length, xCoordBytes.length); System.arraycopy(yCoordBytes, 0, uncompressedPoint, 1 + 2 * fieldSize - yCoordBytes.length, yCoordBytes.length); uncompressedPoint[0] = 0x04; return uncompressedPoint; } /** * Decompress public key. * * @param compressedKey the compressed public key * @param ecParameters the elliptic curve parameters * @return uncompressed encoded public key */ private static byte[] decompressPubkey(byte[] compressedKey, ECParameterSpec ecParameters ) { EllipticCurve curve = ecParameters.getCurve(); // Check array length and type indicator byte if (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) ECFieldFp ecf = (ECFieldFp) curve.getField(); BigInteger modulus = ecf.getP(); BigInteger pow = modulus.add(BigInteger.ONE).shiftRight(2); BigInteger temp = xCoord.pow(2).add(curve.getA()); temp = temp.multiply(xCoord); temp = temp.add(curve.getB()); temp = temp.modPow(pow, modulus); boolean tempIsOdd = temp.testBit(0); boolean yShouldBeOdd = compressedKey[0] == 3; if (tempIsOdd != yShouldBeOdd) { temp = temp.negate().mod(modulus); } final BigInteger yCoord = temp; final byte[] yCoordBytes = toUnsignedBytes(yCoord); int fieldSize = (curve.getField().getFieldSize() + 7) / 8; byte[] result = new byte[2 * fieldSize + 1]; System.arraycopy(compressedKey, 0,result, 1 + fieldSize - compressedKey.length, compressedKey.length); System.arraycopy(yCoordBytes, 0,result, 1 + 2 * fieldSize - yCoordBytes.length, yCoordBytes.length); // set uncompressed key indicator result[0] = 0x04; return result; } /** * To unsigned bytes. * * @param i the i * @return the unsigned bytes of the integer */ 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; } }