Skip to content

MASTG-DEMO-0071: References to Asymmetric Key Pairs Used For Multiple Purposes with Semgrep

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

Sample

This sample generates an RSA key pair using KeyGenParameterSpec with multiple purposes: PURPOSE_SIGN, PURPOSE_VERIFY, PURPOSE_ENCRYPT, and PURPOSE_DECRYPT. It subsequently uses it for encryption, decryption, signing, and verification.

  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
    }
}
  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
package org.owasp.mastestapp;

import android.content.Context;
import android.icu.util.GregorianCalendar;
import android.security.keystore.KeyGenParameterSpec;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.Charsets;
import kotlin.text.StringsKt;

/* compiled from: MastgTest.kt */
@Metadata(m69d1 = {"\u00008\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0006\n\u0002\u0010\u0012\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000b\n\u0002\b\u0002\b\u0007\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\u0012\u0010\f\u001a\u0004\u0018\u00010\r2\b\u0010\u000e\u001a\u0004\u0018\u00010\rJ\u0010\u0010\u000f\u001a\u0004\u0018\u00010\r2\u0006\u0010\u000e\u001a\u00020\rJ\u0006\u0010\u0010\u001a\u00020\u0011J\n\u0010\u0012\u001a\u0004\u0018\u00010\u0013H\u0002J\u0006\u0010\u0014\u001a\u00020\u0006J\u000e\u0010\u0015\u001a\u00020\r2\u0006\u0010\u000e\u001a\u00020\rJ\u0016\u0010\u0016\u001a\u00020\u00172\u0006\u0010\u000e\u001a\u00020\r2\u0006\u0010\u0018\u001a\u00020\rR\u000e\u0010\u0005\u001a\u00020\u0006X\u0082D¢\u0006\u0002\n\u0000R\u000e\u0010\u0007\u001a\u00020\u0006X\u0082D¢\u0006\u0002\n\u0000R\u000e\u0010\b\u001a\u00020\u0006X\u0082D¢\u0006\u0002\n\u0000R\u000e\u0010\t\u001a\u00020\u0006X\u0082D¢\u0006\u0002\n\u0000R\u000e\u0010\n\u001a\u00020\u0006X\u0082D¢\u0006\u0002\n\u0000R\u000e\u0010\u000b\u001a\u00020\u0006X\u0082D¢\u0006\u0002\n\u0000R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0019"}, m70d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "(Landroid/content/Context;)V", "ALGORITHM", "", "ANDROID_KEYSTORE", "BLOCK_MODE", "KEY_ALIAS", "PADDING", "TRANSFORMATION", "decrypt", "", "data", "encrypt", "generateKey", "Ljava/security/KeyPair;", "getCertificate", "Ljava/security/cert/X509Certificate;", "mastgTest", "sign", "verify", "", "signature", "app_debug"}, m71k = 1, m72mv = {1, 9, 0}, m74xi = 48)
/* loaded from: classes4.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final String ALGORITHM;
    private final String ANDROID_KEYSTORE;
    private final String BLOCK_MODE;
    private final String KEY_ALIAS;
    private final String PADDING;
    private final String TRANSFORMATION;
    private final Context context;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
        this.ANDROID_KEYSTORE = "AndroidKeyStore";
        this.KEY_ALIAS = "MultiPurposeKey";
        this.ALGORITHM = "RSA";
        this.BLOCK_MODE = "ECB";
        this.PADDING = "PKCS1Padding";
        this.TRANSFORMATION = this.ALGORITHM + '/' + this.BLOCK_MODE + '/' + this.PADDING;
    }

    public final String mastgTest() {
        String str;
        generateKey();
        byte[] data = "secret".getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(data, "this as java.lang.String).getBytes(charset)");
        String logs = "Original Data: '" + new String(data, Charsets.UTF_8) + "'\n\n";
        byte[] encryptedData = encrypt(data);
        String encryptedDataPreview = encryptedData != null ? StringsKt.take(new String(encryptedData, Charsets.UTF_8), 7) : null;
        if (encryptedData == null || encryptedDataPreview == null) {
            return "FAILURE - Encryption failed.";
        }
        String logs2 = logs + "Encrypted data preview: " + encryptedDataPreview + "...\n";
        byte[] decryptedData = decrypt(encryptedData);
        String logs3 = logs2 + "Decrypted data: " + (decryptedData != null ? new String(decryptedData, Charsets.UTF_8) : null) + "\n\n";
        byte[] signature = sign(data);
        String signaturePreview = StringsKt.take(new String(signature, Charsets.UTF_8), 10);
        StringBuilder append = new StringBuilder().append(((logs3 + "Signing data...\n") + "Signature preview: " + signaturePreview + "...\n") + "Verifying signature...\n");
        if (verify(data, signature)) {
            str = "Verification result: Signature is correct\n\n";
        } else {
            str = "Verification result: Signature is invalid\n\n";
        }
        String logs4 = append.append(str).toString();
        return logs4 + "SUCCESS!!";
    }

    public final KeyPair generateKey() {
        KeyStore ks = KeyStore.getInstance(this.ANDROID_KEYSTORE);
        ks.load(null);
        if (ks.containsAlias(this.KEY_ALIAS)) {
            ks.deleteEntry(this.KEY_ALIAS);
        }
        GregorianCalendar startDate = new GregorianCalendar();
        GregorianCalendar endDate = new GregorianCalendar();
        endDate.add(1, 1);
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", this.ANDROID_KEYSTORE);
        Intrinsics.checkNotNullExpressionValue(keyPairGenerator, "getInstance(...)");
        KeyGenParameterSpec.Builder $this$generateKey_u24lambda_u242 = new KeyGenParameterSpec.Builder(this.KEY_ALIAS, 15);
        $this$generateKey_u24lambda_u242.setCertificateSerialNumber(BigInteger.valueOf(777L));
        $this$generateKey_u24lambda_u242.setCertificateSubject(new X500Principal("CN=" + this.KEY_ALIAS));
        $this$generateKey_u24lambda_u242.setDigests("SHA-256");
        $this$generateKey_u24lambda_u242.setSignaturePaddings("PKCS1");
        $this$generateKey_u24lambda_u242.setEncryptionPaddings("PKCS1Padding");
        $this$generateKey_u24lambda_u242.setCertificateNotBefore(startDate.getTime());
        $this$generateKey_u24lambda_u242.setCertificateNotAfter(endDate.getTime());
        KeyGenParameterSpec parameterSpec = $this$generateKey_u24lambda_u242.build();
        Intrinsics.checkNotNullExpressionValue(parameterSpec, "run(...)");
        keyPairGenerator.initialize(parameterSpec);
        KeyPair genKeyPair = keyPairGenerator.genKeyPair();
        Intrinsics.checkNotNullExpressionValue(genKeyPair, "genKeyPair(...)");
        return genKeyPair;
    }

    public final byte[] encrypt(byte[] data) {
        Intrinsics.checkNotNullParameter(data, "data");
        try {
            X509Certificate cert = getCertificate();
            Intrinsics.checkNotNull(cert);
            Cipher cipher = Cipher.getInstance(this.TRANSFORMATION);
            cipher.init(1, cert.getPublicKey());
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public final byte[] decrypt(byte[] data) {
        try {
            KeyStore ks = KeyStore.getInstance(this.ANDROID_KEYSTORE);
            ks.load(null);
            Key key = ks.getKey(this.KEY_ALIAS, null);
            Intrinsics.checkNotNull(key, "null cannot be cast to non-null type java.security.PrivateKey");
            PrivateKey privateKey = (PrivateKey) key;
            Cipher cipher = Cipher.getInstance(this.TRANSFORMATION);
            cipher.init(2, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public final byte[] sign(byte[] data) {
        Intrinsics.checkNotNullParameter(data, "data");
        KeyStore ks = KeyStore.getInstance(this.ANDROID_KEYSTORE);
        ks.load(null);
        Key key = ks.getKey(this.KEY_ALIAS, null);
        Intrinsics.checkNotNull(key, "null cannot be cast to non-null type java.security.PrivateKey");
        PrivateKey privateKey = (PrivateKey) key;
        Signature sig = Signature.getInstance("SHA256withRSA");
        sig.initSign(privateKey);
        sig.update(data);
        byte[] sign = sig.sign();
        Intrinsics.checkNotNullExpressionValue(sign, "sign(...)");
        return sign;
    }

    public final boolean verify(byte[] data, byte[] signature) {
        Intrinsics.checkNotNullParameter(data, "data");
        Intrinsics.checkNotNullParameter(signature, "signature");
        Signature sig = Signature.getInstance("SHA256withRSA");
        X509Certificate certificate = getCertificate();
        sig.initVerify(certificate != null ? certificate.getPublicKey() : null);
        sig.update(data);
        return sig.verify(signature);
    }

    private final X509Certificate getCertificate() {
        KeyStore ks = KeyStore.getInstance(this.ANDROID_KEYSTORE);
        ks.load(null);
        Certificate certificate = ks.getCertificate(this.KEY_ALIAS);
        if (certificate instanceof X509Certificate) {
            return (X509Certificate) certificate;
        }
        return null;
    }
}

Steps

Run the semgrep rule, as defined below, against the sample code.

../../../../rules/mastg-android-asymmetric-key-pair-used-for-multiple-purposes.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
rules:
  - id: mastg-android-asymmetric-key-pair-used-for-multiple-purposes
    severity: WARNING
    languages:
      - java
    metadata:
      summary: Detects usage of KeyGenParameterSpec.Builder to collect observations for key-purposes evaluation.
    message: |
      [MASVS-CRYPTO-1] Detected usage of KeyGenParameterSpec.Builder. Review the configured purposes to ensure key separation (avoid mixing encryption/decryption with signing/verification or wrapping).

    # Match any construction of KeyGenParameterSpec.Builder, capturing alias and purposes.
    pattern: |
      new KeyGenParameterSpec.Builder($ALIAS, $PURPOSES)
run.sh
1
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-asymmetric-key-pair-used-for-multiple-purposes.yml ./MastgTest_reversed.java > output.txt

Observation

The rule flags the constructor call to KeyGenParameterSpec.Builder in the decompiled Java code. This includes the key alias and a number representing the combined purposes used during key generation.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌────────────────┐
 1 Code Finding 
└────────────────┘

    MastgTest_reversed.java
    ❯❱ rules.mastg-android-asymmetric-key-pair-used-for-multiple-purposes
          [MASVS-CRYPTO-1] Detected usage of KeyGenParameterSpec.Builder. Review the configured purposes to
          ensure key separation (avoid mixing encryption/decryption with signing/verification or wrapping).

           83 KeyGenParameterSpec.Builder $this$generateKey_u24lambda_u242 = new
               KeyGenParameterSpec.Builder(this.KEY_ALIAS, 15);                  

Evaluation

The test fails because the key is configured for multiple purposes.

In the output, we can see how the constructor receives a combined purpose value of 15, which is the bitwise OR of PURPOSE_ENCRYPT (1), PURPOSE_DECRYPT (2), PURPOSE_SIGN (4), and PURPOSE_VERIFY (8).