Java crypto is like an architect building a house. Architect only knows how to design a house, but needs a construction company to actually build it. In Java, only crypto interface is defined, and a provider is required to actually implement the methods. There are different
providers
, but one has stood out in the test of times.
BouncyCastle
De facto open source standard for the crypto implementation is
BouncyCastle
(BC). It is maintained for both Java and C# and trusted by sources like Android and
OkHttp
.
BouncyCastle and Android
Due to Android including a cut down version of BC in the SDK, the Australian crypto provider could not at all be used as a provider in earlier phone systems. The package names are the same and would clash. To fix this problem, since Android 3, the phone’s internal copy
was
renamed to “com.android.org.bouncycastle”.
However, if using
"BC"
as the crypto provider:
Mac.getInstance("HmacSHA256", "BC")
$ openssl ecparam -genkey -out testsk.pem -name prime256v1
$ openssl ec -in testsk.pem -pubout -text
Private-Key: (256 bit)
priv:
6c:ab:c1:a8:59:64:9d:b7:58:e9:25:bc:35:53:f1:
4f:4e:30:65:de:b3:c6:cf:7e:f8:31:cb:14:a6:93:
55:a3
04:6c:e1:2c:7a:bd:ff:05:31:98:ed:e8:8a:bc:d3:
c3:1c:ee:00:ae:59:8b:a4:6a:1b:f9:61:c9:09:fe:
b3:b0:1e:53:1f:af:56:6b:af:9b:4b:db:14:73:4b:
f0:ad:c3:56:18:13:bf:9f:2f:40:fc:26:ab:2b:fe:
cf:d6:4f:02:42
// the first 04 is the header byte
val rawPublicKey = Bytes("046ce12c7abdff053198ede88abcd3c31cee00ae598ba46a1bf961c909feb3b01e531faf566baf9b4bdb14734bf0adc3561813bf9f2f40fc26ab2bfecfd64f0242")
val params = ECNamedCurveTable.getParameterSpec("secp256r1")
val keySpec =
ECPublicKeySpec(params.curve.decodePoint(rawPublicKey.byteArray), params)
val keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider())
val javaPublicKey = keyFactory.generatePublic(keySpec) as ECPublicKey
val convertedBack = Bytes(65)
convertedBack.set(0, 0x04)
// the BigInteger can sometimes be 31 bytes. Then 0x00 needs to be prepended
convertedBack.set(1, publicPoint.affineX.toBytes(32))
convertedBack.set(32, publicPoint.affineY.toBytes(32))
require(rawPublicKey == convertedBack)
val rawPrivateKey = Bytes("6cabc1a859649db758e925bc3553f14f4e3065deb3c6cf7ef831cb14a69355a3").byteArray
val d = BigInteger(1, rawPrivateKey)
val curveSpec = ECParameterSpec(params.curve, params.g, params.n, params.h)
val privateKeySpec = ECPrivateKeySpec(d, curveSpec)
val keyFactory = KeyFactory.getInstance("EC")
val privateKey = keyFactory.generatePrivate(privateKeySpec) as ECPrivateKey
// assure the d value is always 32 bytes long
val convertedBack = privateKey.d.toBytes(32)
require(rawPrivateKey == convertedBack)
val message = Bytes("aabb")
val signer = Signature.getInstance("SHA256withPLAIN-ECDSA", BouncyCastleProvider())
signer.initSign(privateKey)
signer.update(message.byteArray)
val signature = signer.sign()
val verifier = Signature.getInstance("SHA256withPLAIN-ECDSA", BouncyCastleProvider())
verifier.initVerify(publicKey)
verifier.update(message.byteArray)
val result = verifier.verify(signature)
require(result == true)
val message = byteArrayOf(0, 1, 2)
val key: SecretKey = SecretKeySpec(sharedSecretKey, "HmacSHA256")
val mac: Mac = Mac.getInstance("HmacSHA256", BouncyCastleProvider())
mac.init(key)
val hmac = mac.doFinal(message)
There is the more modern Tink, which promises a cleaner and harder to misuse API. It is from Google developers and supported on multiple platforms, including Java and Android. This is a welcome change to Java’s crypto space, where currently the HMAC instance has to be initialised with a String value:
SecretKeySpec(sharedSecretKey, "HmacSHA256")
Since Java doesn’t provide the implementation for the crypto functions, a separate provider needs to be used. BouncyCastle is suitable for this task. Unfortunately, in Android, it is already included in the SDK. Namespace conflicts arise and the real BC provider can be used by inputting BouncyCastleProvider()
instead of "BC"
.
Sample code: github
hmkit-crypto-telematics: github