libeufin

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

CryptoUtil.kt (13531B)


      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 interface NBO {
     56     fun nbo(): ByteArray
     57 
     58     fun signNbo(key: ByteArray): EddsaSignature = CryptoUtil.eddsaSign(this.nbo(), key)
     59 
     60     fun verifyNbo(signature: EddsaSignature, key: EddsaPublicKey): Boolean = CryptoUtil.checkEdssaSignature(this.nbo(), signature, key)
     61 }
     62 
     63 /** Helpers for dealing with cryptographic operations in EBICS / LibEuFin */
     64 object CryptoUtil {
     65 
     66     private val provider = BouncyCastleProvider()
     67 
     68     /** Load an RSA private key from its binary PKCS#8 encoding  */
     69     fun loadRSAPrivate(encodedPrivateKey: ByteArray): RSAPrivateCrtKey {
     70         val spec = PKCS8EncodedKeySpec(encodedPrivateKey)
     71         val priv = KeyFactory.getInstance("RSA").generatePrivate(spec)
     72         return priv as RSAPrivateCrtKey
     73     }
     74 
     75     /** Load an RSA public key from its binary X509 encoding */
     76     fun loadRSAPublic(encodedPublicKey: ByteArray): RSAPublicKey {
     77         val spec = X509EncodedKeySpec(encodedPublicKey)
     78         val pub = KeyFactory.getInstance("RSA").generatePublic(spec)
     79         return pub as RSAPublicKey
     80     }
     81 
     82     /** Create an RSA public key from its components: [modulus] and [exponent] */
     83     fun RSAPublicFromComponents(modulus: ByteArray, exponent: ByteArray): RSAPublicKey {
     84         val modulusBigInt = BigInteger(1, modulus)
     85         val exponentBigInt = BigInteger(1, exponent)
     86         val spec = RSAPublicKeySpec(modulusBigInt, exponentBigInt)
     87         return KeyFactory.getInstance("RSA").generatePublic(spec) as RSAPublicKey
     88     }
     89 
     90     /** Extract an RSA public key from a [raw] X.509 certificate */
     91     fun RSAPublicFromCertificate(raw: ByteArray): RSAPublicKey {
     92         val certificate = CertificateFactory.getInstance("X.509").generateCertificate(raw.inputStream())
     93         return certificate.publicKey as RSAPublicKey
     94     }
     95 
     96     /** Generate an RSA public key from a [private] one */
     97     fun RSAPublicFromPrivate(private: RSAPrivateCrtKey): RSAPublicKey {
     98         val spec = RSAPublicKeySpec(private.modulus, private.publicExponent)
     99         return KeyFactory.getInstance("RSA").generatePublic(spec) as RSAPublicKey
    100     }
    101 
    102     /** Generate a self-signed X.509 certificate from an RSA [private] key */
    103     fun X509CertificateFromRSAPrivate(private: RSAPrivateCrtKey, name: String): X509Certificate {
    104         val start = Date()
    105         val calendar = Calendar.getInstance()
    106         calendar.time = start
    107         calendar.add(Calendar.YEAR, 1_000)
    108         val end = calendar.time
    109 
    110         val name = X500Name("CN=$name")
    111         val builder = JcaX509v3CertificateBuilder(
    112             name,
    113             BigInteger(20, Random()),
    114             start,
    115             end,
    116             name,
    117             RSAPublicFromPrivate(private)
    118         )
    119 
    120 
    121         builder.addExtension(
    122             Extension.keyUsage, true, KeyUsage(
    123                 KeyUsage.digitalSignature
    124                         or KeyUsage.nonRepudiation
    125                         or KeyUsage.keyEncipherment
    126                         or KeyUsage.dataEncipherment
    127                         or KeyUsage.keyAgreement
    128                         or KeyUsage.keyCertSign
    129                         or KeyUsage.cRLSign
    130                         or KeyUsage.encipherOnly
    131                         or KeyUsage.decipherOnly
    132             )
    133         )
    134         builder.addExtension(Extension.basicConstraints, true, BasicConstraints(true))
    135 
    136         val certificate = JcaContentSignerBuilder("SHA256WithRSA").build(private)
    137         return JcaX509CertificateConverter()
    138             .setProvider(provider)
    139             .getCertificate(builder.build(certificate))
    140 
    141     }
    142 
    143     /** Generate an RSA key pair of [keysize] */
    144     fun genRSAPair(keysize: Int): Pair<RSAPrivateCrtKey, RSAPublicKey> {
    145         val gen = KeyPairGenerator.getInstance("RSA")
    146         gen.initialize(keysize)
    147         val pair = gen.genKeyPair()
    148         return Pair(pair.private as RSAPrivateCrtKey, pair.public as RSAPublicKey)
    149     }
    150 
    151     /** Generate an RSA private key of [keysize] */
    152     fun genRSAPrivate(keysize: Int): RSAPrivateCrtKey = genRSAPair(keysize).first
    153 
    154     /** Generate an RSA public key of [keysize] */
    155     fun genRSAPublic(keysize: Int): RSAPublicKey = genRSAPair(keysize).second
    156 
    157     /**
    158      * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3).
    159      */
    160     fun getEbicsPublicKeyHash(publicKey: RSAPublicKey): ByteArray {
    161         val keyBytes = ByteArrayOutputStream()
    162         keyBytes.writeBytes(publicKey.publicExponent.encodeHex().trimStart('0').toByteArray())
    163         keyBytes.write(' '.code)
    164         keyBytes.writeBytes(publicKey.modulus.encodeHex().trimStart('0').toByteArray())
    165         val digest = MessageDigest.getInstance("SHA-256")
    166         return digest.digest(keyBytes.toByteArray())
    167     }
    168 
    169     fun genEbicsE002Key(encryptionPublicKey: RSAPublicKey): Pair<SecretKey, ByteArray> {
    170         // Gen transaction key
    171         val keygen = KeyGenerator.getInstance("AES", provider)
    172         keygen.init(128)
    173         val transactionKey = keygen.generateKey()
    174         // Encrypt transaction keyA
    175         val cipher = Cipher.getInstance(
    176             "RSA/None/PKCS1Padding",
    177             provider
    178         )
    179         cipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey)
    180         val encryptedTransactionKey = cipher.doFinal(transactionKey.encoded)
    181         return Pair(transactionKey, encryptedTransactionKey)
    182     }
    183 
    184     /**
    185      * Encrypt data according to the EBICS E002 encryption process.
    186      */
    187     fun encryptEbicsE002(
    188         transactionKey: SecretKey,
    189         data: InputStream
    190     ): CipherInputStream {
    191         val cipher = Cipher.getInstance(
    192             "AES/CBC/X9.23Padding",
    193             provider
    194         )
    195         val ivParameterSpec = IvParameterSpec(ByteArray(16))
    196         cipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec)
    197         return CipherInputStream(data, cipher)
    198     }
    199 
    200     fun decryptEbicsE002Key(
    201         privateKey: RSAPrivateCrtKey,
    202         encryptedTransactionKey: ByteArray
    203     ): SecretKeySpec {
    204         val cipher = Cipher.getInstance(
    205             "RSA/None/PKCS1Padding",
    206             provider
    207         )
    208         cipher.init(Cipher.DECRYPT_MODE, privateKey)
    209         val transactionKeyBytes = cipher.doFinal(encryptedTransactionKey)
    210         return SecretKeySpec(transactionKeyBytes, "AES")
    211     }
    212 
    213     fun decryptEbicsE002(
    214         transactionKey: SecretKeySpec,
    215         encryptedData: InputStream
    216     ): CipherInputStream {
    217         val cipher = Cipher.getInstance(
    218             "AES/CBC/X9.23Padding",
    219             provider
    220         )
    221         val ivParameterSpec = IvParameterSpec(ByteArray(16))
    222         cipher.init(Cipher.DECRYPT_MODE, transactionKey, ivParameterSpec)
    223         return CipherInputStream(encryptedData, cipher)
    224     }
    225 
    226     /**
    227      * Signing algorithm corresponding to the EBICS A006 signing process.
    228      *
    229      * Note that while [data] can be arbitrary-length data, in EBICS, the order
    230      * data is *always* hashed *before* passing it to the signing algorithm, which again
    231      * uses a hash internally.
    232      */
    233     fun signEbicsA006(data: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray {
    234         val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
    235         signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
    236         signature.initSign(privateKey)
    237         signature.update(data)
    238         return signature.sign()
    239     }
    240 
    241     fun verifyEbicsA006(sig: ByteArray, data: ByteArray, publicKey: RSAPublicKey): Boolean {
    242         val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
    243         signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
    244         signature.initVerify(publicKey)
    245         signature.update(data)
    246         return signature.verify(sig)
    247     }
    248 
    249     fun digestEbicsOrderA006(orderData: ByteArray): ByteArray {
    250         val digest = MessageDigest.getInstance("SHA-256")
    251         for (b in orderData) {
    252             when (b) {
    253                 '\r'.code.toByte(), '\n'.code.toByte(), (26).toByte() -> Unit
    254                 else -> digest.update(b)
    255             }
    256         }
    257         return digest.digest()
    258     }
    259 
    260     fun decryptKey(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey {
    261         /* make key out of passphrase */
    262         val pbeKeySpec = PBEKeySpec(passphrase.toCharArray())
    263         val keyFactory = SecretKeyFactory.getInstance(data.algName)
    264         val secretKey = keyFactory.generateSecret(pbeKeySpec)
    265         /* Make a cipher */
    266         val cipher = Cipher.getInstance(data.algName)
    267         cipher.init(
    268             Cipher.DECRYPT_MODE,
    269             secretKey,
    270             data.algParameters // has hash count and salt
    271         )
    272         /* Ready to decrypt */
    273         val decryptedKeySpec: PKCS8EncodedKeySpec = data.getKeySpec(cipher)
    274         val priv = KeyFactory.getInstance("RSA").generatePrivate(decryptedKeySpec)
    275         return priv as RSAPrivateCrtKey
    276     }
    277 
    278     fun hashStringSHA256(input: String): ByteArray =
    279         MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8))
    280 
    281     fun hashStringNbo(input: String): ByteArray =
    282         MessageDigest.getInstance("SHA-512").digest(input.toByteArray(Charsets.UTF_8) + byteArrayOf(0)).sliceArray(0..31)
    283 
    284     fun bcrypt(password: String, salt: ByteArray, cost: Int): ByteArray {
    285         val pwBytes = BCrypt.passwordToByteArray(password.toCharArray())
    286         return BCrypt.generate(pwBytes, salt, cost)
    287     }
    288 
    289     fun hashArgon2id(password: String, salt: ByteArray): ByteArray {
    290         // OSWAP recommended config https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
    291         val builder = Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
    292             .withIterations(1)
    293             .withMemoryAsKB(47104)
    294             .withParallelism(1)
    295             .withSalt(salt)
    296 
    297         val gen = Argon2BytesGenerator()
    298         gen.init(builder.build())
    299 
    300         val result = ByteArray(32)
    301         gen.generateBytes(password.toCharArray(), result, 0, result.size)
    302         return result
    303     }
    304 
    305     private fun mfaBodyHash(body: ByteArray, salt: Base32Crockford16B): Base32Crockford64B {
    306         val digest = MessageDigest.getInstance("SHA-512")
    307         digest.update(salt.raw)
    308         val hash = digest.digest(body)
    309         return Base32Crockford64B(hash)
    310     }
    311 
    312     fun mfaBodyHashCheck(body: ByteArray, hash: Base32Crockford64B, salt: Base32Crockford16B): Boolean {
    313         val check = mfaBodyHash(body, salt)
    314         return check == hash
    315     }
    316 
    317     fun mfaBodyHashCreate(body: ByteArray): Pair<Base32Crockford64B, Base32Crockford16B> {
    318         val salt = Base32Crockford16B.secureRand()
    319         val hash = mfaBodyHash(body, salt)
    320         return Pair(hash, salt)
    321     }
    322 
    323     fun checkEdssaSignature(data: ByteArray, signature: EddsaSignature, publicKey: EddsaPublicKey): Boolean {
    324         val pubKey = Ed25519PublicKeyParameters(publicKey.raw, 0)
    325 
    326         val verifier = Ed25519Signer()
    327         verifier.init(false, pubKey)
    328         verifier.update(data, 0, data.size)
    329 
    330         return verifier.verifySignature(signature.raw)
    331     }
    332 
    333     fun eddsaSign(data: ByteArray, privateKey: ByteArray): EddsaSignature {
    334         val privateKey = Ed25519PrivateKeyParameters(privateKey, 0)
    335 
    336         val signer = Ed25519Signer()
    337         signer.init(true, privateKey)
    338         signer.update(data, 0, data.size)
    339 
    340         return EddsaSignature(signer.generateSignature())
    341     }
    342 }