I've written up a small wrapper for RSA and AES encryption in Kotlin, and I want to make sure I'm not making any glaring mistakes before use it. This is going on a small project with basically no needed security, but I wanted to try and write it correctly.
Here's my Person
class:
class Person private constructor(val name :String,
private val keyPair: KeyPair) {
val publicKey : PublicKey
get() = keyPair.public
private val privateKey : PrivateKey
get() = keyPair.private ?: error("Current Person implementation doesn't support functions that use the private key")
fun isValid() = fromPrivateKey(name,privateKey).publicKey == publicKey
override fun toString(): String {
return "Person(name=$name, finger=${fingerprint().substring(0,10)})"
}
fun sign(data :ByteArray): Signature {
sig.initSign(privateKey)
sig.update(data)
return Signature(sig.sign())
}
fun fingerprint():String = DigestUtils.sha1Hex(publicKey.encoded)
/** This needs to be below 245 bytes */
fun encrypt(data :ByteArray, publicKey: PublicKey): ByteArray {
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
/** This needs to be below 245 bytes */
fun encrypt(data :ByteArray, person :Person):ByteArray = encrypt(data,person.publicKey)
/** This needs to be below 245 bytes */
fun decrypt(encrypted: ByteArray): ByteArray {
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
return cipher.doFinal(encrypted)
}
/** This will encrypt over 245 bytes */
fun encryptAES(data :ByteArray,person :Person):EncryptedData = encryptAES(data,person.publicKey)
/** This will encrypt over 245 bytes */
fun encryptAES(data :ByteArray, publicKey : PublicKey): EncryptedData {
val iv = ByteArray(16) { -1 }
SecureRandom.getInstanceStrong().nextBytes(iv)
val keyGen = KeyGenerator.getInstance("AES")
keyGen.init(128)
val secretKey = keyGen.generateKey()
val ivParameterSpec = IvParameterSpec(iv)
val aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
val final = aesCipher.doFinal(data)
return EncryptedData(iv, encrypt(secretKey.encoded,publicKey), final)
//need to return encrypted secret key and the encrypted message
}
fun decryptAES(data : EncryptedData) :ByteArray{
val iv = data.iv
val ivParameterSpec = IvParameterSpec(iv)
val decryptedSecretKey = decrypt(data.encryptedSecretKey)
val secretKey = SecretKeySpec(decryptedSecretKey, 0, decryptedSecretKey.size, "AES")
val aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
aesCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)
return aesCipher.doFinal(data.encryptedData)
}
And then I use the Companion object to make new instances of this class:
companion object {
private val sig = java.security.Signature.getInstance("SHA1WithRSA")
fun verify(publicKey: PublicKey, signature: Signature, data: ByteArray): Boolean {
sig.initVerify(publicKey)
sig.update(data)
return sig.verify(signature.byteArray)
}
//buildKeyPair(DigestUtils.sha1(name)!!.contentHashCode().toLong())
private fun buildKeyPair(seed :Long): KeyPair {
val random = SecureRandom.getInstance("SHA1PRNG")
random.setSeed(seed)
val keySize = 2048
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(keySize,random)
return keyPairGenerator.genKeyPair()
}
private fun buildKeyPair():KeyPair {
val keySize = 2048
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(keySize)
return keyPairGenerator.genKeyPair()
}
//generators
fun fromKeyPair(name :String, keyPair: KeyPair):Person = Person(name, keyPair)
fun fromPublicKey(name :String, publicKey: PublicKey):Person = Person(name,KeyPair(publicKey,null))
fun fromPrivateKey(name :String, privateKey: PrivateKey):Person {
//attempt to find the correct public key
if(privateKey !is RSAPrivateCrtKey)
error("Private key is not a RSAPrivateCrtKey and does not contain enough data to compute the public key")
val spec = RSAPublicKeySpec(privateKey.modulus,privateKey.publicExponent)
val factory = KeyFactory.getInstance("RSA")
val publicKey = factory.generatePublic(spec)
return Person(name, KeyPair(publicKey,privateKey))
}
fun deterministicFromName(name :String) :Person = Person(name,buildKeyPair(DigestUtils.sha1(name)!!.contentHashCode().toLong()))
fun generateNew(name :String) :Person = Person(name, buildKeyPair())
}
This also uses these classes:
class Signature(val byteArray: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Signature
if (!byteArray.contentEquals(other.byteArray)) return false
return true
}
override fun hashCode(): Int {
return byteArray.contentHashCode()
}
}
and
class EncryptedData(val iv :ByteArray, val encryptedSecretKey :ByteArray,val encryptedData :ByteArray)
I'm using RSA keys to encrypt a randomly generated AES key, which can be used to encrypt data that is larger than 245 bytes.
The idea is that I can pass public keys around but treat everything as a Person
, and then throw an error if the private key is invalid. Is this a good design?