2020-07-03 01:45:18 +02:00
|
|
|
package dorkbox.network.other
|
|
|
|
|
|
|
|
import java.math.BigInteger
|
2020-09-19 22:07:09 +02:00
|
|
|
import java.security.GeneralSecurityException
|
|
|
|
import java.security.KeyFactory
|
|
|
|
import java.security.KeyPair
|
|
|
|
import java.security.KeyPairGenerator
|
|
|
|
import java.security.PrivateKey
|
|
|
|
import java.security.SecureRandom
|
2020-07-03 01:45:18 +02:00
|
|
|
import java.security.interfaces.ECPrivateKey
|
|
|
|
import java.security.interfaces.ECPublicKey
|
2020-09-19 22:07:09 +02:00
|
|
|
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
|
2020-08-12 23:35:31 +02:00
|
|
|
import javax.crypto.Cipher
|
|
|
|
|
2020-07-03 01:45:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2020-09-19 22:07:09 +02:00
|
|
|
private object CryptoEccNative {
|
2020-07-03 01:45:18 +02:00
|
|
|
// 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
|
|
|
|
|
2020-08-12 23:35:31 +02:00
|
|
|
// More info about 25519 key types (ed25519 and X25519)
|
|
|
|
// https://blog.filippo.io/using-ed25519-keys-for-encryption/
|
|
|
|
|
2020-07-03 01:45:18 +02:00
|
|
|
|
|
|
|
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<String>) {
|
2020-08-12 23:35:31 +02:00
|
|
|
val cryptoText = "i23j4jh234kjh234kjh23lkjnfa9s8egfuypuh325"
|
|
|
|
|
2020-07-03 01:45:18 +02:00
|
|
|
// NOTE: THIS IS NOT 25519!!
|
|
|
|
println("Generate ECPublicKey from PrivateKey (String) for curve secp256k1 (final)")
|
|
|
|
println("Check keys with https://gobittest.appspot.com/Address")
|
2020-08-12 23:35:31 +02:00
|
|
|
|
2020-07-03 01:45:18 +02:00
|
|
|
// 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))
|
2020-08-12 23:35:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
// 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}")
|
2020-07-03 01:45:18 +02:00
|
|
|
}
|
|
|
|
}
|