Skip to content

MASTG-DEMO-0072: Runtime Use of Asymmetric Key Pairs Used For Multiple Purposes With Frida

Download MASTG-DEMO-0072 APK Open MASTG-DEMO-0072 Folder Build MASTG-DEMO-0072 APK

Sample

This sample uses the code from References to Asymmetric Key Pairs Used For Multiple Purposes with Semgrep and takes a dynamic approach to intercept the cryptographic operations at runtime (including encryption, decryption, signing, and verification) to demonstrate the misuse of an asymmetric key pair for multiple purposes.

../MASTG-DEMO-0071/MastgTest.kt
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package org.owasp.mastestapp

import android.content.Context
import android.icu.util.Calendar
import android.icu.util.GregorianCalendar
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.math.BigInteger
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.PrivateKey
import java.security.Signature
import java.security.cert.X509Certificate
import javax.crypto.Cipher
import javax.security.auth.x500.X500Principal

/**
 * A utility class to demonstrate and test RSA key management, encryption, and signing
 * using the Android Keystore system.
 */
class MastgTest(private val context: Context) {

    // --- Keystore and Algorithm Constants ---
    private val ANDROID_KEYSTORE = "AndroidKeyStore"
    private val KEY_ALIAS = "MultiPurposeKey"

    private val ALGORITHM = KeyProperties.KEY_ALGORITHM_RSA
    private val BLOCK_MODE = KeyProperties.BLOCK_MODE_ECB
    private val PADDING = KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1

    // Combined transformation string for Cipher
    private val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"

    /**
     * Executes the main test flow: key generation, encryption/decryption, and signing/verification.
     * @return A log string detailing the test results.
     */
    fun mastgTest(): String {
        // Ensure key is generated and stored in Keystore
        generateKey()

        val data = "secret".toByteArray()
        var logs = "Original Data: '${data.toString(Charsets.UTF_8)}'\n\n"

        // 1. Encryption and Decryption Test
        val encryptedData = encrypt(data)
        val encryptedDataPreview = encryptedData?.toString(Charsets.UTF_8)?.take(7)

        if (encryptedData == null || encryptedDataPreview == null) {
            return "FAILURE - Encryption failed."
        }
        logs += "Encrypted data preview: $encryptedDataPreview...\n"

        val decryptedData = decrypt(encryptedData)
        logs += "Decrypted data: ${decryptedData?.toString(Charsets.UTF_8)}\n\n"


        // 2. Signing and Verification Test (using original data)
        val signature = sign(data)
        val signaturePreview = signature.toString(Charsets.UTF_8).take(10)

        logs += "Signing data...\n"
        logs += "Signature preview: $signaturePreview...\n"
        logs += "Verifying signature...\n"

        logs += if (verify(data, signature)) {
            "Verification result: Signature is correct\n\n"
        } else {
            "Verification result: Signature is invalid\n\n"
        }

        logs += "SUCCESS!!"
        return logs
    }

    /**
     * Generates an RSA KeyPair in the Android Keystore with multiple purposes.
     * Deletes the existing key if an alias conflict exists.
     */
    fun generateKey(): KeyPair {
        val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
        // Clean up previous entry
        if (ks.containsAlias(KEY_ALIAS)) {
            ks.deleteEntry(KEY_ALIAS)
        }

        // Define key validity period
        val startDate = GregorianCalendar()
        val endDate = GregorianCalendar().apply { add(Calendar.YEAR, 1) }

        // Initialize key generator for RSA
        val keyPairGenerator: KeyPairGenerator = KeyPairGenerator
            .getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE)

        // Build key generation specification
        val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY or
                    KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
        ).run {
            setCertificateSerialNumber(BigInteger.valueOf(777))
            setCertificateSubject(X500Principal("CN=$KEY_ALIAS"))
            setDigests(KeyProperties.DIGEST_SHA256)
            setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
            setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
            setCertificateNotBefore(startDate.time)
            setCertificateNotAfter(endDate.time)
            build()
        }

        // Generate the key pair
        keyPairGenerator.initialize(parameterSpec)
        return keyPairGenerator.genKeyPair()
    }

    /**
     * Encrypts the provided data using the public key from the Keystore.
     * @param data The plaintext data to encrypt.
     * @return The encrypted byte array, or null on failure.
     */
    fun encrypt(data: ByteArray): ByteArray? {
        return try {
            val cert = getCertificate()!!
            val cipher = Cipher.getInstance(TRANSFORMATION)
            cipher.init(Cipher.ENCRYPT_MODE, cert.publicKey)
            cipher.doFinal(data)
        } catch (e: Exception) {
            // Log exceptions during encryption
            e.printStackTrace()
            null
        }
    }

    /**
     * Decrypts the provided data using the private key from the Keystore.
     * @param data The encrypted byte array.
     * @return The decrypted byte array, or null on failure.
     */
    fun decrypt(data: ByteArray?): ByteArray? {
        return try {
            val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
            val privateKey = ks.getKey(KEY_ALIAS, null) as PrivateKey
            val cipher = Cipher.getInstance(TRANSFORMATION)
            cipher.init(Cipher.DECRYPT_MODE, privateKey)
            cipher.doFinal(data)
        } catch (e: Exception) {
            // Log exceptions during decryption
            e.printStackTrace()
            null
        }
    }

    /**
     * Signs the provided data using the private key from the Keystore.
     * @param data The data to be signed.
     * @return The signature byte array.
     */
    fun sign(data: ByteArray): ByteArray {
        val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
        val privateKey = ks.getKey(KEY_ALIAS, null) as PrivateKey
        val sig = Signature.getInstance("SHA256withRSA")
        sig.initSign(privateKey)
        sig.update(data)
        return sig.sign()
    }

    /**
     * Verifies a signature against the provided data using the public key (Certificate).
     * @param data The original data.
     * @param signature The signature to verify.
     * @return True if the signature is valid, false otherwise.
     */
    fun verify(data: ByteArray, signature: ByteArray): Boolean {
        val sig = Signature.getInstance("SHA256withRSA")
        // Initialize with public key for verification
        sig.initVerify(getCertificate()?.publicKey)
        sig.update(data)
        return sig.verify(signature)
    }

    /**
     * Retrieves the X.509 Certificate (containing the public key) from the Keystore.
     * @return The certificate, or null if not found.
     */
    private fun getCertificate(): X509Certificate? {
        val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
        return ks.getCertificate(KEY_ALIAS) as? X509Certificate
    }
}

Steps

  1. Install the app on a device ( Installing Apps)
  2. Make sure you have Frida for Android installed on your machine and the frida-server running on the device
  3. Run run.sh to spawn the app with Frida
  4. Click the Start button
  5. Stop the script by pressing Ctrl+C and/or q to quit the Frida CLI
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
if (Java.available) {
    Java.perform(function() {

        // --- Configuration Constants ---
        const CRYPTO_OP_ENCRYPT_DECRYPT = "encryption/decryption";
        const CRYPTO_OP_SIGN_VERIFY = "sign/verify";

        // Storage for tracking key usage: { key_toString: usage_type }
        const usedKeys = {};

        // --- Core Reflection Setup Function ---
        /**
         * Initializes and returns the reflected Method object for Object.toString().
         */
        function getToStringMethodRef() {
            try {
                const Object = Java.use('java.lang.Object');
                const toStringMethod = Object.class.getDeclaredMethod("toString", []);
                toStringMethod.setAccessible(true);
                return toStringMethod;
            } catch (e) {
                console.log("āŒ CRITICAL SETUP ERROR: Failed to prepare toString reflection: " + e);
                return null;
            }
        }

        // Execute the reflection setup once at the start
        const toStringMethodRef = getToStringMethodRef();

        if (!toStringMethodRef) {
            console.log("āŒ Script halted: Reflection setup failed.");
            return;
        }

        try {
            // --- Java Class References ---
            const Cipher = Java.use("javax.crypto.Cipher");
            const Signature = Java.use("java.security.Signature");

            // --- Helper Functions ---

            /**
             * Safely invokes the Java toString() method on the key/certificate and logs usage.
             */
            function logKeyDetails(key, certificate, cryptoOp) {

                // If a certificate is provided, use its PublicKey for tracking
                let trackingKey = key;
                if (certificate) {
                    try {
                        trackingKey = certificate.getPublicKey();
                        console.log("  Tracking Key: Certificate's Public Key");
                    } catch (e) {
                        console.log(`  ERROR: Failed to get Public Key from Certificate: ${e}`);
                        return;
                    }
                }

                if (trackingKey === null) {
                    console.log("  ERROR: Key object is null, skipping tracking.");
                    return;
                }

                let javaToStringResult = "UNKNOWN_KEY_ID";

                try {
                    // Invoke toString() using the pre-calculated reference.
                    // Pass the instance (trackingKey) and an empty array of arguments ([]).
                    javaToStringResult = toStringMethodRef.invoke(trackingKey, []);
                } catch (e) {
                    // Fallback to native pointer if reflection fails
                    try {
                        javaToStringResult = trackingKey.toPointer().toString();
                    } catch (pe) {
                        javaToStringResult = `ERROR_ID_${Date.now()}`;
                    }
                    console.log(`  WARNING: Reflection failed. Tracking ID: ${javaToStringResult}`);
                }

                console.log(`  ${cryptoOp} with key: "${javaToStringResult}"`);

                // --- Usage Tracking Logic ---
                const storedOp = usedKeys[javaToStringResult];

                if (storedOp !== undefined && storedOp !== cryptoOp) {
                    console.log("!!! WARNING: This key is used for multiple, conflicting purposes: " + storedOp + " and " + cryptoOp);
                } else if (storedOp === undefined) {
                    // Store the key's first detected usage
                    usedKeys[javaToStringResult] = cryptoOp;
                }
            }

            /**
             * Logs the current Java stack trace.
             */
            function logStackTrace() {
                console.log("  Stack Trace:");
                const exception = Java.use("java.lang.Exception").$new();
                const stackTraceElements = exception.getStackTrace();
                for (let i = 0; i < stackTraceElements.length; i++) {
                    const element = stackTraceElements[i];
                    if (i < 10) { // Limit stack depth for cleaner output
                        console.log("    " + element.toString());
                    }
                }
                console.log("  --- End Stack Trace ---");
            }


            // --- HOOKS: Cipher (Encryption/Decryption) ---

            Cipher.init.overload('int', 'java.security.Key').implementation = function(opmode, key) {
                console.log("\nšŸ”’ *** Cipher.init(Key) HOOKED ***");
                logKeyDetails(key, null, CRYPTO_OP_ENCRYPT_DECRYPT);
                logStackTrace();
                this.init(opmode, key);
            };

            Cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function(opmode, certificate) {
                console.log("\nšŸ“œ *** Cipher.init(Certificate) HOOKED ***");
                logKeyDetails(null, certificate, CRYPTO_OP_ENCRYPT_DECRYPT);
                logStackTrace();
                this.init(opmode, certificate);
            };

            // --- HOOKS: Signature (Sign/Verify) ---

            Signature.initSign.overload('java.security.PrivateKey').implementation = function(key) {
                console.log("\nāœļø *** Signature.initSign(PrivateKey) HOOKED ***");
                logKeyDetails(key, null, CRYPTO_OP_SIGN_VERIFY);
                logStackTrace();
                this.initSign(key);
            };

            Signature.initVerify.overload('java.security.PublicKey').implementation = function(key) {
                console.log("\nāœ… *** Signature.initVerify(PublicKey) HOOKED ***");
                logKeyDetails(key, null, CRYPTO_OP_SIGN_VERIFY);
                logStackTrace();
                this.initVerify(key);
            };

            Signature.initVerify.overload('java.security.cert.Certificate').implementation = function(certificate) {
                console.log("\nšŸ“œ *** Signature.initVerify(Certificate) HOOKED ***");
                logKeyDetails(null, certificate, CRYPTO_OP_SIGN_VERIFY);
                logStackTrace();
                this.initVerify(certificate);
            };

            console.log("āœ… Frida script loaded and cryptographic APIs hooked successfully!");

        } catch (e) {
            console.log("āŒ Failed to load hooks (make sure target process is running): " + e);
        }
    });
} else {
    console.log("āŒ Java is not available. Ensure Frida is attached to a JVM process.");
}
1
2
3
4
5
6
7
#!/bin/bash

frida \
    -U \
    -f org.owasp.mastestapp \
    -l script.js \
    -o output.txt

Observation

The output shows all usages of cryptographic operations.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
āœ… Frida script loaded and cryptographic APIs hooked successfully!

šŸ”’ *** Cipher.init(Key) HOOKED ***
  encryption/decryption with key: "OpenSSLRSAPublicKey{modulus=a41226cf3ca5b7ccaabb65777f0b01938ead4cc0c917a4da5f746edf0bd24c03cdc2c90518b518067298eb3c6f334f3801e2686f018b53f665eccf923d63508248643e3e645021114d4f3055118be2734ef28d980b2e73e96c0cf0fcfe9acd859308fc4748c3fc539259875f45312cd5c300e39b54d643dba7d7347d4aeb5f647aac09fcf189975bdc94e2bb9ad969399f7a2e8f081e8b1e8b3540796440a9bae5daa37acb506b9aa8cad3d9b94104de7cf8cdb604889b59635871bb4909e881be42d5e3e4d219e3d053e753b19fe861864aee6eaf38b5291f60190608db0025c603d3a302d10eccd6606bc7dade641ffec81185606e7b16ca3b88cfc1f048a1,publicExponent=10001}"
  Stack Trace:
    javax.crypto.Cipher.init(Native Method)
    org.owasp.mastestapp.MastgTest.encrypt(MastgTest.kt:126)
    org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:47)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:73)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:71)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M(Clickable.kt:987)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke(Clickable.kt:981)
    androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)
    kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:177)
  --- End Stack Trace ---

šŸ”’ *** Cipher.init(Key) HOOKED ***
  encryption/decryption with key: "android.security.keystore2.AndroidKeyStoreRSAPrivateKey@3818961"
  Stack Trace:
    javax.crypto.Cipher.init(Native Method)
    org.owasp.mastestapp.MastgTest.decrypt(MastgTest.kt:145)
    org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:55)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:73)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:71)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M(Clickable.kt:987)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke(Clickable.kt:981)
    androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)
    kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:177)
  --- End Stack Trace ---

āœļø *** Signature.initSign(PrivateKey) HOOKED ***
  sign/verify with key: "android.security.keystore2.AndroidKeyStoreRSAPrivateKey@3818961"
!!! WARNING: This key is used for multiple, conflicting purposes: encryption/decryption and sign/verify
  Stack Trace:
    java.security.Signature.initSign(Native Method)
    org.owasp.mastestapp.MastgTest.sign(MastgTest.kt:163)
    org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:60)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:73)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:71)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M(Clickable.kt:987)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke(Clickable.kt:981)
    androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)
    kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:177)
  --- End Stack Trace ---

āœ… *** Signature.initVerify(PublicKey) HOOKED ***
  sign/verify with key: "OpenSSLRSAPublicKey{modulus=a41226cf3ca5b7ccaabb65777f0b01938ead4cc0c917a4da5f746edf0bd24c03cdc2c90518b518067298eb3c6f334f3801e2686f018b53f665eccf923d63508248643e3e645021114d4f3055118be2734ef28d980b2e73e96c0cf0fcfe9acd859308fc4748c3fc539259875f45312cd5c300e39b54d643dba7d7347d4aeb5f647aac09fcf189975bdc94e2bb9ad969399f7a2e8f081e8b1e8b3540796440a9bae5daa37acb506b9aa8cad3d9b94104de7cf8cdb604889b59635871bb4909e881be42d5e3e4d219e3d053e753b19fe861864aee6eaf38b5291f60190608db0025c603d3a302d10eccd6606bc7dade641ffec81185606e7b16ca3b88cfc1f048a1,publicExponent=10001}"
!!! WARNING: This key is used for multiple, conflicting purposes: encryption/decryption and sign/verify
  Stack Trace:
    java.security.Signature.initVerify(Native Method)
    org.owasp.mastestapp.MastgTest.verify(MastgTest.kt:177)
    org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:67)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:73)
    org.owasp.mastestapp.MainActivityKt$MyScreenContent$1$1$1.invoke(MainActivity.kt:71)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M(Clickable.kt:987)
    androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke(Clickable.kt:981)
    androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)
    kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:177)
  --- End Stack Trace ---

Note all WARNING messages in the output.

Evaluation

The test fails because the same asymmetric key pair is used across different groups of cryptographic operations.

A single RSA key pair is performing both encryption and decryption, as well as signing and verification, which violates the requirement that an asymmetric key be restricted to one purpose class.

In the sample output:

  • the private key instance (android.security.keystore2.AndroidKeyStoreRSAPrivateKey) is consistently identified by the instance ID @3818961
  • the matching public key instance (OpenSSLRSAPublicKey) is identified by its modulus beginning with a41226cf3ca5b...

These two objects form one key pair in the Android Keystore. Their appearances across different operations show the misuse.

Following the private key reference @3818961, the first usage appears during decryption:

šŸ”’ *** Cipher.init(Key) HOOKED ***
  encryption/decryption with key: "android.security.keystore2.AndroidKeyStoreRSAPrivateKey@3818961"
  Stack Trace:
    javax.crypto.Cipher.init(Native Method)
    org.owasp.mastestapp.MastgTest.decrypt(MastgTest.kt:145)

Later in the output, the same private key instance is used for signing:

āœļø *** Signature.initSign(PrivateKey) HOOKED ***
  sign/verify with key: "android.security.keystore2.AndroidKeyStoreRSAPrivateKey@3818961"
!!! WARNING: This key is used for multiple, conflicting purposes: encryption/decryption and sign/verify

The matching public key, identified by its modulus (a41226cf3ca5b...), appears in both encryption:

šŸ”’ *** Cipher.init(Key) HOOKED ***
  encryption/decryption with key: "OpenSSLRSAPublicKey{modulus=a41226cf3ca5b...

And verification:

āœ… *** Signature.initVerify(PublicKey) HOOKED ***
  sign/verify with key: "OpenSSLRSAPublicKey{modulus=a41226cf3ca5b...