Network/not-fixed/CryptoEccNative.kt

294 lines
12 KiB
Kotlin
Raw Normal View History

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
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
import javax.crypto.Cipher
/**
*
*/
2020-09-19 22:07:09 +02:00
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<String>) {
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}")
}
}