libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

CryptoUtil.kt (13018B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2024, 2026 Taler Systems S.A.
      4 
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9 
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14 
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 package tech.libeufin.common.crypto
     21 
     22 import org.bouncycastle.asn1.x500.X500Name
     23 import org.bouncycastle.asn1.x509.BasicConstraints
     24 import org.bouncycastle.asn1.x509.Extension
     25 import org.bouncycastle.asn1.x509.KeyUsage
     26 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
     27 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
     28 import org.bouncycastle.crypto.generators.Argon2BytesGenerator
     29 import org.bouncycastle.crypto.generators.BCrypt
     30 import org.bouncycastle.crypto.params.Argon2Parameters
     31 import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
     32 import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
     33 import org.bouncycastle.crypto.signers.Ed25519Signer
     34 import org.bouncycastle.jce.provider.BouncyCastleProvider
     35 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
     36 import tech.libeufin.common.*
     37 import java.io.ByteArrayOutputStream
     38 import java.io.InputStream
     39 import java.math.BigInteger
     40 import java.security.KeyFactory
     41 import java.security.KeyPairGenerator
     42 import java.security.MessageDigest
     43 import java.security.Signature
     44 import java.security.cert.CertificateFactory
     45 import java.security.cert.X509Certificate
     46 import java.security.interfaces.RSAPrivateCrtKey
     47 import java.security.interfaces.RSAPublicKey
     48 import java.security.spec.*
     49 import java.util.*
     50 import javax.crypto.*
     51 import javax.crypto.spec.IvParameterSpec
     52 import javax.crypto.spec.PBEKeySpec
     53 import javax.crypto.spec.SecretKeySpec
     54 
     55 /** Helpers for dealing with cryptographic operations in EBICS / LibEuFin */
     56 object CryptoUtil {
     57 
     58     private val provider = BouncyCastleProvider()
     59 
     60     /** Load an RSA private key from its binary PKCS#8 encoding  */
     61     fun loadRSAPrivate(encodedPrivateKey: ByteArray): RSAPrivateCrtKey {
     62         val spec = PKCS8EncodedKeySpec(encodedPrivateKey)
     63         val priv = KeyFactory.getInstance("RSA").generatePrivate(spec)
     64         return priv as RSAPrivateCrtKey
     65     }
     66 
     67     /** Load an RSA public key from its binary X509 encoding */
     68     fun loadRSAPublic(encodedPublicKey: ByteArray): RSAPublicKey {
     69         val spec = X509EncodedKeySpec(encodedPublicKey)
     70         val pub = KeyFactory.getInstance("RSA").generatePublic(spec)
     71         return pub as RSAPublicKey
     72     }
     73 
     74     /** Create an RSA public key from its components: [modulus] and [exponent] */
     75     fun RSAPublicFromComponents(modulus: ByteArray, exponent: ByteArray): RSAPublicKey {
     76         val modulusBigInt = BigInteger(1, modulus)
     77         val exponentBigInt = BigInteger(1, exponent)
     78         val spec = RSAPublicKeySpec(modulusBigInt, exponentBigInt)
     79         return KeyFactory.getInstance("RSA").generatePublic(spec) as RSAPublicKey
     80     }
     81 
     82     /** Extract an RSA public key from a [raw] X.509 certificate */
     83     fun RSAPublicFromCertificate(raw: ByteArray): RSAPublicKey {
     84         val certificate = CertificateFactory.getInstance("X.509").generateCertificate(raw.inputStream())
     85         return certificate.publicKey as RSAPublicKey
     86     }
     87 
     88     /** Generate an RSA public key from a [private] one */
     89     fun RSAPublicFromPrivate(private: RSAPrivateCrtKey): RSAPublicKey {
     90         val spec = RSAPublicKeySpec(private.modulus, private.publicExponent)
     91         return KeyFactory.getInstance("RSA").generatePublic(spec) as RSAPublicKey
     92     }
     93 
     94     /** Generate a self-signed X.509 certificate from an RSA [private] key */
     95     fun X509CertificateFromRSAPrivate(private: RSAPrivateCrtKey, name: String): X509Certificate {
     96         val start = Date()
     97         val calendar = Calendar.getInstance()
     98         calendar.time = start
     99         calendar.add(Calendar.YEAR, 1_000)
    100         val end = calendar.time
    101 
    102         val name = X500Name("CN=$name")
    103         val builder = JcaX509v3CertificateBuilder(
    104             name,
    105             BigInteger(20, Random()),
    106             start,
    107             end, 
    108             name,
    109             RSAPublicFromPrivate(private)
    110         )
    111 
    112         
    113         builder.addExtension(Extension.keyUsage, true, KeyUsage(
    114             KeyUsage.digitalSignature 
    115                 or KeyUsage.nonRepudiation
    116                 or KeyUsage.keyEncipherment
    117                 or KeyUsage.dataEncipherment
    118                 or KeyUsage.keyAgreement
    119                 or KeyUsage.keyCertSign
    120                 or KeyUsage.cRLSign
    121                 or KeyUsage.encipherOnly
    122                 or KeyUsage.decipherOnly
    123         ))
    124         builder.addExtension(Extension.basicConstraints, true, BasicConstraints(true))
    125 
    126         val certificate = JcaContentSignerBuilder("SHA256WithRSA").build(private)
    127         return JcaX509CertificateConverter()
    128             .setProvider(provider)
    129             .getCertificate(builder.build(certificate))
    130 
    131     }
    132 
    133     /** Generate an RSA key pair of [keysize] */
    134     fun genRSAPair(keysize: Int): Pair<RSAPrivateCrtKey, RSAPublicKey> {
    135         val gen = KeyPairGenerator.getInstance("RSA")
    136         gen.initialize(keysize)
    137         val pair = gen.genKeyPair()
    138         return Pair(pair.private as RSAPrivateCrtKey, pair.public as RSAPublicKey)
    139     }
    140     /** Generate an RSA private key of [keysize] */
    141     fun genRSAPrivate(keysize: Int): RSAPrivateCrtKey = genRSAPair(keysize).first
    142     /** Generate an RSA public key of [keysize] */
    143     fun genRSAPublic(keysize: Int): RSAPublicKey = genRSAPair(keysize).second
    144 
    145     /**
    146      * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3).
    147      */
    148     fun getEbicsPublicKeyHash(publicKey: RSAPublicKey): ByteArray {
    149         val keyBytes = ByteArrayOutputStream()
    150         keyBytes.writeBytes(publicKey.publicExponent.encodeHex().trimStart('0').toByteArray())
    151         keyBytes.write(' '.code)
    152         keyBytes.writeBytes(publicKey.modulus.encodeHex().trimStart('0').toByteArray())
    153         val digest = MessageDigest.getInstance("SHA-256")
    154         return digest.digest(keyBytes.toByteArray())
    155     }
    156 
    157     fun genEbicsE002Key(encryptionPublicKey: RSAPublicKey): Pair<SecretKey, ByteArray> {
    158         // Gen transaction key
    159         val keygen = KeyGenerator.getInstance("AES", provider)
    160         keygen.init(128)
    161         val transactionKey = keygen.generateKey()
    162         // Encrypt transaction keyA
    163         val cipher = Cipher.getInstance(
    164             "RSA/None/PKCS1Padding",
    165             provider
    166         )
    167         cipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey)
    168         val encryptedTransactionKey = cipher.doFinal(transactionKey.encoded)
    169         return Pair(transactionKey, encryptedTransactionKey)
    170     }
    171     
    172     /**
    173      * Encrypt data according to the EBICS E002 encryption process.
    174      */
    175     fun encryptEbicsE002(
    176         transactionKey: SecretKey,
    177         data: InputStream
    178     ): CipherInputStream {
    179         val cipher = Cipher.getInstance(
    180             "AES/CBC/X9.23Padding",
    181             provider
    182         )
    183         val ivParameterSpec = IvParameterSpec(ByteArray(16))
    184         cipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec)
    185         return CipherInputStream(data, cipher)
    186     }
    187 
    188     fun decryptEbicsE002Key(
    189         privateKey: RSAPrivateCrtKey,
    190         encryptedTransactionKey: ByteArray
    191     ): SecretKeySpec {
    192         val cipher = Cipher.getInstance(
    193             "RSA/None/PKCS1Padding",
    194             provider
    195         )
    196         cipher.init(Cipher.DECRYPT_MODE, privateKey)
    197         val transactionKeyBytes = cipher.doFinal(encryptedTransactionKey)
    198         return SecretKeySpec(transactionKeyBytes, "AES")
    199     }
    200 
    201     fun decryptEbicsE002(
    202         transactionKey: SecretKeySpec,
    203         encryptedData: InputStream
    204     ): CipherInputStream {
    205         val cipher = Cipher.getInstance(
    206             "AES/CBC/X9.23Padding",
    207             provider
    208         )
    209         val ivParameterSpec = IvParameterSpec(ByteArray(16))
    210         cipher.init(Cipher.DECRYPT_MODE, transactionKey, ivParameterSpec)
    211         return CipherInputStream(encryptedData, cipher)
    212     }
    213 
    214     /**
    215      * Signing algorithm corresponding to the EBICS A006 signing process.
    216      *
    217      * Note that while [data] can be arbitrary-length data, in EBICS, the order
    218      * data is *always* hashed *before* passing it to the signing algorithm, which again
    219      * uses a hash internally.
    220      */
    221     fun signEbicsA006(data: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray {
    222         val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
    223         signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
    224         signature.initSign(privateKey)
    225         signature.update(data)
    226         return signature.sign()
    227     }
    228 
    229     fun verifyEbicsA006(sig: ByteArray, data: ByteArray, publicKey: RSAPublicKey): Boolean {
    230         val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
    231         signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
    232         signature.initVerify(publicKey)
    233         signature.update(data)
    234         return signature.verify(sig)
    235     }
    236 
    237     fun digestEbicsOrderA006(orderData: ByteArray): ByteArray {
    238         val digest = MessageDigest.getInstance("SHA-256")
    239         for (b in orderData) {
    240             when (b) {
    241                 '\r'.code.toByte(), '\n'.code.toByte(), (26).toByte() -> Unit
    242                 else -> digest.update(b)
    243             }
    244         }
    245         return digest.digest()
    246     }
    247 
    248     fun decryptKey(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey {
    249         /* make key out of passphrase */
    250         val pbeKeySpec = PBEKeySpec(passphrase.toCharArray())
    251         val keyFactory = SecretKeyFactory.getInstance(data.algName)
    252         val secretKey = keyFactory.generateSecret(pbeKeySpec)
    253         /* Make a cipher */
    254         val cipher = Cipher.getInstance(data.algName)
    255         cipher.init(
    256             Cipher.DECRYPT_MODE,
    257             secretKey,
    258             data.algParameters // has hash count and salt
    259         )
    260         /* Ready to decrypt */
    261         val decryptedKeySpec: PKCS8EncodedKeySpec = data.getKeySpec(cipher)
    262         val priv = KeyFactory.getInstance("RSA").generatePrivate(decryptedKeySpec)
    263         return priv as RSAPrivateCrtKey
    264     }
    265 
    266     fun hashStringSHA256(input: String): ByteArray =
    267         MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8))
    268 
    269     fun bcrypt(password: String, salt: ByteArray, cost: Int): ByteArray {
    270         val pwBytes = BCrypt.passwordToByteArray(password.toCharArray())
    271         return BCrypt.generate(pwBytes, salt, cost)
    272     }
    273     
    274     fun hashArgon2id(password: String, salt: ByteArray): ByteArray {
    275         // OSWAP recommended config https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
    276         val builder = Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
    277             .withIterations(1)
    278             .withMemoryAsKB(47104)
    279             .withParallelism(1)
    280             .withSalt(salt)
    281 
    282         val gen = Argon2BytesGenerator()
    283         gen.init(builder.build())
    284 
    285         val result = ByteArray(32)
    286         gen.generateBytes(password.toCharArray(), result, 0, result.size)
    287         return result
    288     }
    289 
    290     private fun mfaBodyHash(body: ByteArray, salt: Base32Crockford16B): Base32Crockford64B {
    291         val digest = MessageDigest.getInstance("SHA-512")
    292         digest.update(salt.raw)
    293         val hash = digest.digest(body)
    294         return Base32Crockford64B(hash)
    295     }
    296 
    297     fun mfaBodyHashCheck(body: ByteArray, hash: Base32Crockford64B, salt: Base32Crockford16B): Boolean {
    298         val check = mfaBodyHash(body, salt)
    299         return check == hash
    300     }
    301 
    302     fun mfaBodyHashCreate(body: ByteArray): Pair<Base32Crockford64B, Base32Crockford16B> {
    303         val salt = Base32Crockford16B.secureRand()
    304         val hash = mfaBodyHash(body, salt)
    305         return Pair(hash, salt)
    306     }
    307 
    308     fun checkEdssaSignature(data: ByteArray, signature: EddsaSignature, publicKey: EddsaPublicKey): Boolean {
    309         val pubKey = Ed25519PublicKeyParameters(publicKey.raw, 0)
    310 
    311         val verifier = Ed25519Signer()
    312         verifier.init(false, pubKey)
    313         verifier.update(data, 0, data.size)
    314         
    315         return verifier.verifySignature(signature.raw)
    316     }
    317 
    318     fun eddsaSign(data: ByteArray, privateKey: ByteArray): EddsaSignature {
    319         val privateKey = Ed25519PrivateKeyParameters(privateKey, 0)
    320 
    321         val signer = Ed25519Signer()
    322         signer.init(true, privateKey)
    323         signer.update(data, 0, data.size)
    324 
    325         return EddsaSignature(signer.generateSignature())
    326     }
    327 }