package dorkbox.network.other import java.math.BigInteger import java.security.GeneralSecurityException import java.security.KeyFactory import java.security.KeyPair import java.security.KeyPairGenerator import java.security.PrivateKey import java.security.SecureRandom import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.spec.ECField import java.security.spec.ECFieldFp import java.security.spec.ECParameterSpec import java.security.spec.ECPoint import java.security.spec.ECPublicKeySpec import java.security.spec.EllipticCurve import java.security.spec.NamedParameterSpec import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec import javax.crypto.Cipher /** * */ private object CryptoEccNative { // see: https://openjdk.java.net/jeps/324 const val curve25519 = "curve25519" const val default_curve = curve25519 const val macSize = 512 // on NIST vs 25519 vs Brainpool, see: // - http://ogryb.blogspot.de/2014/11/why-i-dont-trust-nist-p-256.html // - http://credelius.com/credelius/?p=97 // - http://safecurves.cr.yp.to/ // we should be using 25519, because NIST and brainpool are "unsafe". Brainpool is "more random" than 25519, but is still not considered safe. // more info about ECC from: // http://www.johannes-bauer.com/compsci/ecc/?menuid=4 // http://stackoverflow.com/questions/7419183/problems-implementing-ecdh-on-android-using-bouncycastle // http://tools.ietf.org/html/draft-jivsov-openpgp-ecc-06#page-4 // http://www.nsa.gov/ia/programs/suiteb_cryptography/ // https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java // http://nelenkov.blogspot.com/2011/12/using-ecdh-on-android.html // http://www.secg.org/collateral/sec1_final.pdf // More info about 25519 key types (ed25519 and X25519) // https://blog.filippo.io/using-ed25519-keys-for-encryption/ fun createKeyPair(secureRandom: SecureRandom): KeyPair { val kpg: KeyPairGenerator = KeyPairGenerator.getInstance("XDH") kpg.initialize(NamedParameterSpec.X25519, secureRandom) return kpg.generateKeyPair() // println("--- Public Key ---") // val publicKey = kp.public // // System.out.println(publicKey.algorithm) // XDH // System.out.println(publicKey.format) // X.509 // // // save this public key // val pubKey = publicKey.encoded // // println("---") // // println("--- Private Key ---") // val privateKey = kp.private // // System.out.println(privateKey.algorithm); // XDH // System.out.println(privateKey.format); // PKCS#8 // // // save this private key // val priKey = privateKey.encoded // val kf: KeyFactory = KeyFactory.getInstance("XDH"); // //BigInteger u = ... // val pubSpec: XECPublicKeySpec = XECPublicKeySpec(paramSpec, u); // val pubKey: PublicKey = kf.generatePublic(pubSpec); // // // // val ka: KeyAgreement = KeyAgreement.getInstance("XDH"); // ka.init(kp.private); //ka.doPhase(pubKey, true); //byte[] secret = ka.generateSecret(); } private val FieldP_2: BigInteger = BigInteger.TWO // constant for scalar operations private val FieldP_3: BigInteger = BigInteger.valueOf(3) // constant for scalar operations private const val byteVal1 = 1.toByte() @Throws(GeneralSecurityException::class) fun getPublicKey(pk: ECPrivateKey): ECPublicKey? { val params: ECParameterSpec = pk.params val w: ECPoint = scalmultNew(params, params.generator, pk.s) //final ECPoint w = scalmult(params.getCurve(), pk.getParams().getGenerator(), pk.getS()); val kg: KeyFactory = KeyFactory.getInstance("EC") return kg.generatePublic(ECPublicKeySpec(w, params)) as ECPublicKey } private fun scalmultNew(params: ECParameterSpec, g: ECPoint, kin: BigInteger): ECPoint { val curve = params.curve val field = curve.field if (field !is ECFieldFp) throw java.lang.UnsupportedOperationException(field::class.java.canonicalName) val p = field.p val a = curve.a var R = ECPoint.POINT_INFINITY // value only valid for curve secp256k1, code taken from https://www.secg.org/sec2-v2.pdf, // see "Finally the order n of G and the cofactor are: n = "FF.." val SECP256K1_Q = params.order //BigInteger SECP256K1_Q = new BigInteger("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16); var k = kin.mod(SECP256K1_Q) // uses this ! // BigInteger k = kin.mod(p); // do not use this ! wrong as per comment from President James Moveon Polk val length = k.bitLength() val binarray = ByteArray(length) for (i in 0..length - 1) { binarray[i] = k.mod(FieldP_2).byteValueExact() k = k.shiftRight(1) } for (i in length - 1 downTo 0) { R = doublePoint(p, a, R) if (binarray[i] == byteVal1) R = addPoint(p, a, R, g) } return R } fun scalmultOrg(curve: EllipticCurve, g: ECPoint, kin: BigInteger): ECPoint { val field: ECField = curve.getField() if (field !is ECFieldFp) throw UnsupportedOperationException(field::class.java.canonicalName) val p: BigInteger = (field as ECFieldFp).getP() val a: BigInteger = curve.getA() var R = ECPoint.POINT_INFINITY // value only valid for curve secp256k1, code taken from https://www.secg.org/sec2-v2.pdf, // see "Finally the order n of G and the cofactor are: n = "FF.." val SECP256K1_Q = BigInteger("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) var k = kin.mod(SECP256K1_Q) // uses this ! // wrong as per comment from President James Moveon Polk // BigInteger k = kin.mod(p); // do not use this ! println(" SECP256K1_Q: $SECP256K1_Q") println(" p: $p") System.out.println("curve: " + curve.toString()) val length = k.bitLength() val binarray = ByteArray(length) for (i in 0..length - 1) { binarray[i] = k.mod(FieldP_2).byteValueExact() k = k.shiftRight(1) } for (i in length - 1 downTo 0) { R = doublePoint(p, a, R) if (binarray[i] == byteVal1) R = addPoint(p, a, R, g) } return R } // scalar operations for native java // https://stackoverflow.com/a/42797410/8166854 // written by author: SkateScout private fun doublePoint(p: BigInteger, a: BigInteger, R: ECPoint): ECPoint? { if (R == ECPoint.POINT_INFINITY) return R var slope = R.affineX.pow(2).multiply(FieldP_3) slope = slope.add(a) slope = slope.multiply(R.affineY.multiply(FieldP_2).modInverse(p)) val Xout = slope.pow(2).subtract(R.affineX.multiply(FieldP_2)).mod(p) val Yout = R.affineY.negate().add(slope.multiply(R.affineX.subtract(Xout))).mod(p) return ECPoint(Xout, Yout) } private fun addPoint(p: BigInteger, a: BigInteger, r: ECPoint, g: ECPoint): ECPoint? { if (r == ECPoint.POINT_INFINITY) return g if (g == ECPoint.POINT_INFINITY) return r if (r == g || r == g) return doublePoint(p, a, r) val gX = g.affineX val sY = g.affineY val rX = r.affineX val rY = r.affineY val slope = rY.subtract(sY).multiply(rX.subtract(gX).modInverse(p)).mod(p) val Xout = slope.modPow(FieldP_2, p).subtract(rX).subtract(gX).mod(p) var Yout = sY.negate().mod(p) Yout = Yout.add(slope.multiply(gX.subtract(Xout))).mod(p) return ECPoint(Xout, Yout) } private fun byteArrayToHexString(a: ByteArray): String { val sb = StringBuilder(a.size * 2) for (b in a) sb.append(String.format("%02X", b)) return sb.toString() } fun hexStringToByteArray(s: String): ByteArray { val len = s.length val data = ByteArray(len / 2) var i = 0 while (i < len) { data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte() i += 2 } return data } @Throws(GeneralSecurityException::class) @JvmStatic fun main(args: Array) { val cryptoText = "i23j4jh234kjh234kjh23lkjnfa9s8egfuypuh325" // NOTE: THIS IS NOT 25519!! println("Generate ECPublicKey from PrivateKey (String) for curve secp256k1 (final)") println("Check keys with https://gobittest.appspot.com/Address") // https://gobittest.appspot.com/Address val privateKey = "D12D2FACA9AD92828D89683778CB8DFCCDBD6C9E92F6AB7D6065E8AACC1FF6D6" val publicKeyExpected = "04661BA57FED0D115222E30FE7E9509325EE30E7E284D3641E6FB5E67368C2DB185ADA8EFC5DC43AF6BF474A41ED6237573DC4ED693D49102C42FFC88510500799" println("\nprivatekey given : $privateKey") println("publicKeyExpected: $publicKeyExpected") // // routine with bouncy castle // println("\nGenerate PublicKey from PrivateKey with BouncyCastle") // val spec: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("secp256k1") // this ec curve is used for bitcoin operations // val pointQ: org.bouncycastle.math.ec.ECPoint = spec.getG().multiply(BigInteger(1, ch.qos.logback.core.encoder.ByteArrayUtil.hexStringToByteArray(privateKey))) // val publickKeyByte = pointQ.getEncoded(false) // val publicKeyBc: String = byteArrayToHexString(publickKeyByte) // println("publicKeyExpected: $publicKeyExpected") // println("publicKey BC : $publicKeyBc") // println("publicKeys match : " + publicKeyBc.contentEquals(publicKeyExpected)) // regeneration of ECPublicKey with java native starts here println("\nGenerate PublicKey from PrivateKey with Java native routines") // the preset "303E.." only works for elliptic curve secp256k1 // see answer by user dave_thompson_085 // https://stackoverflow.com/questions/48832170/generate-ec-public-key-from-byte-array-private-key-in-native-java-7 val privateKeyFull = "303E020100301006072A8648CE3D020106052B8104000A042730250201010420" + privateKey val privateKeyFullByte: ByteArray = hexStringToByteArray(privateKeyFull) println("privateKey full : $privateKeyFull") val keyFactory = KeyFactory.getInstance("EC") val privateKeyNative: PrivateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(privateKeyFullByte)) val ecPrivateKeyNative = privateKeyNative as ECPrivateKey val ecPublicKeyNative = getPublicKey(ecPrivateKeyNative) val ecPublicKeyNativeByte = ecPublicKeyNative!!.encoded val testPubKey = keyFactory.generatePublic(X509EncodedKeySpec(ecPublicKeyNativeByte)) as ECPublicKey val equal = ecPublicKeyNativeByte.contentEquals(testPubKey.encoded) val publicKeyNativeFull: String = byteArrayToHexString(ecPublicKeyNativeByte) val publicKeyNativeHeader = publicKeyNativeFull.substring(0, 46) val publicKeyNativeKey = publicKeyNativeFull.substring(46, 176) println("ecPublicKeyFull : $publicKeyNativeFull") println("ecPublicKeyHeader: $publicKeyNativeHeader") println("ecPublicKeyKey : $publicKeyNativeKey") println("publicKeyExpected: $publicKeyExpected") println("publicKeys match : " + publicKeyNativeKey.contentEquals(publicKeyExpected)) // encrypt val encryptCipher: Cipher = Cipher.getInstance("RSA") encryptCipher.init(Cipher.ENCRYPT_MODE, ecPublicKeyNative) val cipherText: ByteArray = encryptCipher.doFinal(cryptoText.toByteArray()) // decrypt val decryptCipher = Cipher.getInstance("RSA"); decryptCipher.init(Cipher.DECRYPT_MODE, ecPrivateKeyNative); val outputBytes = decryptCipher.doFinal(cipherText) println("Crypto round passed: ${String(outputBytes) == cryptoText}") } }