package com.tangem.domain.cardReader; import android.util.Log; import com.tangem.util.Util; import org.spongycastle.asn1.ASN1EncodableVector; import org.spongycastle.asn1.ASN1Integer; import org.spongycastle.asn1.DERSequence; import org.spongycastle.jce.ECNamedCurveTable; import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; import org.spongycastle.jce.spec.ECPrivateKeySpec; import org.spongycastle.jce.spec.ECPublicKeySpec; import org.spongycastle.math.ec.ECPoint; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * Created by dvol on 14.11.2017. */ public class CardCrypto { public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception { if( publicKeyArray==null ) throw new Exception("Public key not specified!"); ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); KeyFactory factory = KeyFactory.getInstance("EC", "SC"); ECPoint p1 = spec.getCurve().decodePoint(publicKeyArray); ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); return factory.generatePublic(keySpec); } public static boolean VerifySignature(byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception { Signature signatureInstance = Signature.getInstance("SHA256withECDSA"); PublicKey publicKey = LoadPublicKey(publicKeyArray); signatureInstance.initVerify(publicKey); signatureInstance.update(data); ASN1EncodableVector v = new ASN1EncodableVector(); int size = signature.length / 2; v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, 0, size)))); v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, size, size * 2)))); byte[] sigDer = new DERSequence(v).getEncoded(); return signatureInstance.verify(sigDer); } public static byte[] Signature(byte[] privateKeyArray, byte[] data) throws Exception { ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); KeyFactory factory = KeyFactory.getInstance("EC", "SC"); ECPrivateKeySpec keySpecP = new ECPrivateKeySpec(new BigInteger(1,privateKeyArray), spec); Signature signature = Signature.getInstance("SHA256withECDSA"); PrivateKey privateKey = factory.generatePrivate(keySpecP); signature.initSign(privateKey); signature.update(data); byte[] enc = signature.sign(); if (enc[0] != 0x30) throw new Exception("bad encoding 1"); if ((enc[1] & 0x80) != 0) throw new Exception("unsupported length encoding 1"); if (enc[2] != 0x02) throw new Exception("bad encoding 2"); if ((enc[3] & 0x80) != 0) throw new Exception("unsupported length encoding 2"); int rLength = enc[3]; if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3"); if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3"); int sLength = enc[5 + rLength]; int sPos = 6 + rLength; byte[] res = new byte[64]; if (rLength <= 32) { System.arraycopy(enc, 4, res, 32-rLength, rLength); rLength=32; } else if (rLength == 33 && enc[4] == 0) { rLength--; System.arraycopy(enc, 5, res, 0, rLength); } else { Log.e("cardCrypto","r-length:" + String.valueOf(rLength)); Log.e("cardCrypto","s-length:" + String.valueOf(sLength)); Log.e("cardCrypto","enc:" + Util.bytesToHex(enc)); throw new Exception("unsupported r-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)); } if (sLength <= 32) { System.arraycopy(enc, sPos, res, rLength+32-sLength, sLength); sLength=32; } else if (sLength == 33 && enc[sPos] == 0) { System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1); } else { Log.e("cardCrypto","s-length:" + String.valueOf(sLength)); Log.e("cardCrypto","r-length:" + String.valueOf(rLength)); Log.e("cardCrypto","enc:" +Util.bytesToHex(enc)); throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)); } if(!VerifySignature(GeneratePublicKey(privateKeyArray), data, res)) { throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)+",res:"+Util.bytesToHex(res)); } return res; } public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws NoSuchProviderException, NoSuchAlgorithmException { ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1,privateKeyArray)).getEncoded(false); return publicKeyArray; } /** * Computes the PBKDF2 hash of a password. * * @param password the password to hash. * @param salt the salt * @param iterations the iteration count (slowness factor) * @return the PBDKF2 hash of the password */ public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations) throws InvalidKeyException { return PBKDF2.deriveKey(password, salt, iterations); } public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); byte[] mEncryptedData = cipher.doFinal(data); return mEncryptedData; } public static byte[] Decrypt(byte[] key, byte[] data, boolean UsePKCS7) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException { if (UsePKCS7) { SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length)); return decryptedData; } else { SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING"); Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length)); return decryptedData; } } }