This shows you the differences between two versions of the page.
smd:laboratoare:07 [2020/04/05 14:31] vlad.traista [Resources and useful links] |
smd:laboratoare:07 [2022/04/27 23:37] (current) florin.mihalache [Task 8 - Generate and Verify HMAC (2p)] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Lab 7 - Cryptography ====== | ====== Lab 7 - Cryptography ====== | ||
+ | <note>This lab doesn't cover yet the newly introduced APIs for encrypted files and Shared Preferences - [[https://developer.android.com/topic/security/data|Jetpack Security library]]</note> | ||
===== Objectives ===== | ===== Objectives ===== | ||
* Protect the app's data using encryption | * Protect the app's data using encryption | ||
* Use Android's Keystore to generate and store cryptographic keys | * Use Android's Keystore to generate and store cryptographic keys | ||
+ | * Protect the app's APK using signing | ||
+ | * Use Android's KeyStore to generate and store cryptographic keys and use them to sign an app | ||
+ | * Verify message integrity using HMAC (hash-based message authentication code) | ||
+ | * generate HMAC to sign a message | ||
+ | * verify HMAC to check that the received message was not modified | ||
+ | |||
+ | |||
+ | <note> **Lab's task** | ||
+ | |||
+ | In this lab we create an **app that stores our pins and/or passwords**. | ||
+ | |||
+ | It must have the following features and **UI components**: | ||
+ | * Allow the user to input the description (purpose) of the pin/password | ||
+ | * Allow the user to input the pin/password | ||
+ | * A button to save the input data | ||
+ | * A button to show the description and the value of the saved pin/password | ||
+ | * optional - save more than one description & pin pair and show them in a list | ||
+ | * Can be used only if the device is protected by a Lock Screen | ||
+ | |||
+ | You can create your own project, or use the one provided by us {{:smd:laboratoare:lab7-skel.zip|archive}} [[https://github.com/SMD-UPB/labs/tree/main/lab7/skel|github]] | ||
+ | |||
+ | </note> | ||
+ | |||
+ | **Target API >= 23**. - the reason is provided in the [[.07#android_keystore|Android Keystore]] section | ||
+ | |||
+ | ===== Keyguard ===== | ||
+ | |||
+ | If you are Android users you might have noticed that certain apps require you to setup a lock screen for your device if you don't have one already. | ||
+ | |||
+ | Android's Keyguard handles the lock/unlocking of the device. The SDK offers the [[https://developer.android.com/reference/android/app/KeyguardManager|KeyguardManager]] system service using which you can check if the device is secured with a screen lock and if it's locked or not. | ||
+ | |||
+ | To interact with this service, you need to obtain it using the Context's method ''getSystemService'' and providing it a string representing the service, in this case the [[https://developer.android.com/reference/android/content/Context.html#KEYGUARD_SERVICE|Context.KEYGUARD_SERVICE]] constant. You have done something similar, in a previous lab for obtaining Connectivity Service. | ||
+ | |||
+ | <code Java> | ||
+ | // assuming this is called from an Activity, which is a Context | ||
+ | keyguardService = (KeyguardService) getSystemService(Context.KEYGUARD_SERVICE); | ||
+ | </code> | ||
+ | |||
+ | |||
+ | ==== Task 1 - Extra security (1p) ==== | ||
+ | |||
+ | To improve the app's security allow access to the app only if the device is protected by a Lock screen. Don't allow the use of the app unless it is set. | ||
+ | * Hint: use ''KeyguardManager.isDeviceSecure'' method | ||
+ | * Show an Alert Dialog ([[https://developer.android.com/guide/topics/ui/dialogs#AlertDialog|tutorial]]) informing the users that they need to setup their Lock screen. | ||
+ | * for the ''setPositiveButton'' send an implicit intent with the action ''DevicePolicyManager.ACTION_SET_NEW_PASSWORD'' | ||
+ | * for the ''setNegativeButton'' close the app (call ''finish()'') | ||
+ | * You can perform this check when the activity is resuming. | ||
+ | |||
+ | |||
+ | If you skip this task and your phone is not secured, the decryption from the other tasks might throw an Exception unless you set a flag like in this [[https://stackoverflow.com/a/42277111|solution]]. | ||
===== Android Keystore ===== | ===== Android Keystore ===== | ||
- | The Android Keystore system ([[https://developer.android.com/training/articles/keystore|tutorial]], [[https://developer.android.com/reference/java/security/KeyStore.html|documentation]]) provides mechanisms for securing storing and for generating the cryptographic keys needed by an app. Its role as a security container is guaranteed by the fact that the keys cannot be extracted from the application's process and from the device. | + | The Android Keystore system ([[https://developer.android.com/training/articles/keystore|tutorial]], [[https://developer.android.com/reference/java/security/KeyStore.html|documentation]]) provides mechanisms for securing storage and for generating the cryptographic keys needed by an app. Its role as a security container is guaranteed by the fact that the keys cannot be extracted from the application's process and from the device. |
//Why do we need this?// | //Why do we need this?// | ||
Line 46: | Line 97: | ||
* [[https://developer.android.com/reference/java/security/KeyStore.html#getKey(java.lang.String,%20char[])|getKey(alias, password)]] - the private key or the secret key | * [[https://developer.android.com/reference/java/security/KeyStore.html#getKey(java.lang.String,%20char[])|getKey(alias, password)]] - the private key or the secret key | ||
* [[https://developer.android.com/reference/java/security/KeyStore.html#getCertificate(java.lang.String)|getCertificate(alias)]].getPublicKey() - the public key | * [[https://developer.android.com/reference/java/security/KeyStore.html#getCertificate(java.lang.String)|getCertificate(alias)]].getPublicKey() - the public key | ||
- | * [[https://developer.android.com/reference/java/security/KeyStore.html#containsAlias(java.lang.String)|containsAlias]] - to check if a key with a given alias exists in the keystore | + | * [[https://developer.android.com/reference/java/security/KeyStore.html#containsAlias(java.lang.String)|containsAlias]] - to check if a key with a given alias exists in the Keystore |
- | :!: If you have not provided a password to the keystore, when you get the keys just provide //null// as the //password// parameter. | + | :!: If you have not provided a password to the Keystore, when you get the keys just provide //null// as the //password// parameter. |
**To generate the keys (API > 23):** | **To generate the keys (API > 23):** | ||
- | - Obtain a KeyPairGenerator with a given algorithm and provider ([[https://developer.android.com/training/articles/keystore#SupportedKeyPairGenerators|list of supported algorithms]]) | + | - Obtain a //KeyPairGenerator// with a given algorithm and provider ([[https://developer.android.com/training/articles/keystore#SupportedKeyPairGenerators|list of supported algorithms]]) |
- | - Create a KeyGenParameterSpec for configuring all the parameters need for the keys (e.g. size, dates, purpose) | + | - Create a //KeyGenParameterSpec// for configuring all the parameters need for the keys (e.g. size, dates, purpose) |
* ! we can limit the usage of a key by providing a specific purpose (available purposes are defined in [[https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder|Builder]] docs) | * ! we can limit the usage of a key by providing a specific purpose (available purposes are defined in [[https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder|Builder]] docs) | ||
- Provide the key specification to the key generator using one of the [[https://developer.android.com/reference/java/security/KeyPairGenerator.html#initialize(java.security.spec.AlgorithmParameterSpec)|initialize]] methods | - Provide the key specification to the key generator using one of the [[https://developer.android.com/reference/java/security/KeyPairGenerator.html#initialize(java.security.spec.AlgorithmParameterSpec)|initialize]] methods | ||
Line 68: | Line 119: | ||
// (3) | // (3) | ||
generator.initialize(keyGenParameterSpec); | generator.initialize(keyGenParameterSpec); | ||
+ | |||
// (4) | // (4) | ||
generator.generateKeyPair(); | generator.generateKeyPair(); | ||
Line 74: | Line 126: | ||
**To store the keys:** | **To store the keys:** | ||
- | * No need to do call anything explicitly, they are automatically saved by the ''generateKeyPair()'' of the KeyPairGenerator. | + | * No need to call anything explicitly, they are automatically saved by the ''generateKeyPair()'' of the KeyPairGenerator. |
+ | |||
+ | ==== Task 2 - Keystore (2p) ==== | ||
+ | |||
+ | When the app starts, initialize the Keystore and generate a pair of keys if they don't exist yet. | ||
+ | |||
+ | * Generate a pair of asymmetric keys with the following configuration: | ||
+ | * purposes: ENCRYPT, DECRYPT | ||
+ | * RSA | ||
+ | * key size 2048 | ||
+ | * encryption paddings: RSA_PCKS1 (use **setEncryptionPaddings**) | ||
+ | * **Generate the keys only once**, not every time you start the app | ||
+ | * check if the keys exist using the containsAlias method provided by the KeyStore | ||
+ | * Use the AndroidKeyStore provider when initializing the Keystore, like in the we showed in this section. | ||
+ | * //Design Advice:// separate the crypto logic from the UI logic. In MainActivity just call methods of a separate class, that handles the initialization of the keystore, the generation of keys etc. | ||
+ | |||
===== Encrypt and decrypt data ===== | ===== Encrypt and decrypt data ===== | ||
- | Using the keys obtained from the Keystore, we can perform cryptographic transformations on our data using the Cipher class. | + | Using the keys obtained from the Keystore, we can perform cryptographic transformations on our data using the [[https://developer.android.com/reference/javax/crypto/Cipher|Cipher]] class. |
In the following example we encrypt and decrypt data using a symmetric key algorithm. For an asymmetric-key algorithm the code is similar, it differs just the initialization of the Cipher and that we provide the public key for encryption and the private one for decryption. | In the following example we encrypt and decrypt data using a symmetric key algorithm. For an asymmetric-key algorithm the code is similar, it differs just the initialization of the Cipher and that we provide the public key for encryption and the private one for decryption. | ||
Line 86: | Line 153: | ||
<code java> | <code java> | ||
// The KeyPairGenerator was configured with the spec: | // The KeyPairGenerator was configured with the spec: | ||
- | // * KeyProperties.KEY_ALGORITHM_AES (symmetric-key algorithm) | + | // * KeyProperties.KEY_ALGORITHM_RSA (asymmetric-key algorithm) |
- | // * KeyProperties.BLOCK_MODE_GCM | + | // * KeyProperties. ENCRYPTION_PADDING_RSA_PKCS1 |
- | // * KeyProperties.ENCRYPTION_PADDING_NONE | + | Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); //<-- provide the same configuration! |
- | Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); //<-- provide the same configuration! | + | |
// Encrypt | // Encrypt | ||
- | cipher.init(Cipher.ENCRYPT_MODE, secretKey); | + | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
byte[] bytes = cipher.doFinal(data); | byte[] bytes = cipher.doFinal(data); | ||
String encodedData = Base64.encodeToString(bytes, Base64.DEFAULT); | String encodedData = Base64.encodeToString(bytes, Base64.DEFAULT); | ||
// Decrypt | // Decrypt | ||
- | cipher.init(Cipher.DECRYPT_MODE, secretKey); | + | cipher.init(Cipher.DECRYPT_MODE, privateKey); |
byte[] encryptedData = Base64.decode(encodedData, Base64.DEFAULT); | byte[] encryptedData = Base64.decode(encodedData, Base64.DEFAULT); | ||
String data = new String(cipher.doFinal(encryptedData)); | String data = new String(cipher.doFinal(encryptedData)); | ||
Line 104: | Line 170: | ||
</code> | </code> | ||
+ | ==== Task 3 - Encryption (2p) ==== | ||
+ | * When the user presses the //"Save"// button encrypt the data provided by the user and store them as SharedPreferences | ||
+ | * Initialise the Cipher with the following algorithm/mode/padding string: "RSA/ECB/PKCS1Padding" | ||
- | ===== Tasks ===== | + | ==== Task 4 - Decryption (1p) ==== |
- | In this lab we create an app that stores our **pins** and/or **passwords**. It must have the following features and UI components: | + | * When the user presses the //"Show pin"// button load the data from SharedPreferences and decrypt it |
- | * Allow the user to input the description (purpose) of the pin/password | + | |
- | * Allow the user to input the pin/password | + | |
- | * A button to save the input data | + | |
- | * A button to show the description and the value of the saved pin/password | + | |
- | * optional - save more than one description & pin pair and show them in a list | + | |
- | Target API >= 23. | + | ==== Task 5 (1p - optional) ==== |
+ | * Save more than one description & pin pair and show them in a list. | ||
- | ==== Task 1 - Extra security (2p) ==== | + | <note tip>If you generate the keys with a certain parameter configuration and then you change it, you need to clear the application's data in order for the encryption/decryption to work because you reuse the keys already existing in the keystore. You can also uninstall the app in order to clear the data. Running the app from the Android Studio's run button won't clear the app's data.</note> |
- | To improve the app's security allow access to the app only if the device is protected by a Lock screen. Don't allow the use of the app unless it is set. | + | ===== Application signing ===== |
- | * Use the [[https://developer.android.com/reference/android/app/KeyguardManager|KeyguardManager]] system service | + | |
- | * system services are obtained using the Context's method ''getSystemService'' and providing it a string representing the service, in this case the [[https://developer.android.com/reference/android/content/Context.html#KEYGUARD_SERVICE|Context.KEYGUARD_SERVICE]] constant. | + | |
- | * for API >=23 use ''KeyguardManager.isDeviceSecure'' method | + | |
- | * Show an Alert Dialog ([[https://developer.android.com/guide/topics/ui/dialogs#AlertDialog|tutorial]]) informing the users that they need to setup their Lock screen. | + | |
- | * for the ''setPositiveButton'' send an implicit intent with the action ''DevicePolicyManager.ACTION_SET_NEW_PASSWORD'' | + | |
- | * for the ''setNegativeButton'' close the app (call ''finish()'') | + | |
- | If you skip this task, the decryption might throw an Exception (see [[https://stackoverflow.com/a/42277111|solution]]). | + | If you want to install an application on Android, the //apk// must be digitally signed with a certificate. For example, when you test your application on the emulator, Android Studio signs the apk with a debug certificate. The first time when you run or [[https://developer.android.com/studio/publish/app-signing#debug-mode|debug a project in Android Studio]], a debug keystore and certificate is automatically created using the Android SDK tools in **$HOME/.android/debug.keystore**. Also the keystore is initialized and the key password is set. |
+ | |||
+ | As a security measure the debug certificate needs to be used only for testing and for debug builds. This certificate is not secure for using on app stores. | ||
- | ==== Task 2 - Keystore (3p) ==== | + | For later runs/debugs Android Studio automatically stores the debug signing configuration so that we do not need to enter it every time we launch the app. The signing configuration contains the keystore location //$HOME/.android/debug.keystore//, //keystore password//, //key name// and //key password//. This debug signing configuration used at run/debug is not available for editing. You can create a signing config for your release builds. |
- | * Generate a pair of asymmetric keys with the following configuration: | + | Steps for generating and uploading key and keystore: |
- | * purposes: ENCRYPT, DECRYPT | + | - Go to **Build > Generate Signed Bundle/APK** |
- | * RSA | + | - Select **APK** |
- | * key size 2048 | + | - Under Key store path choose **Create new** |
- | * encryption paddings: RSA_PCKS1 | + | - Complete the fields and then continue with the signing steps below |
- | * Generate the keys only once, not every time you start the app (check if the keys exist using the containsAlias method provided by the KeyStore | + | |
- | * Use the AndroidKeyStore provider when configuring the KeyStore | + | From here we continue to sign the app with the key stored in the newly created keystore. You can skip the first two steps if you are already in the window at the 3rd step: |
- | + | - **Build -> Generate Signed Bundle/APK** | |
+ | - In the **Generate Signed Bundle/APK** choose **APK** | ||
+ | - The app module should be selected if not choose it or choose a module from the drop down | ||
+ | - Enter the path to your keystore, the alias for the key and the passwords for keystore and key | ||
+ | - Enter destination folder for the signed app, enter the release build type, choose the flavor | ||
+ | - Choose the [[https://source.android.com/security/apksigning#v2|APK Signature version]] your app to support - v2 for Android 7.0+ | ||
+ | - Finish | ||
- | ==== Task 3 - Encryption (2.5p) ==== | + | For more details on application signing and distribution check [[https://developer.android.com/studio/publish/app-signing|App Signing]] |
- | * When the user presses the "Save" button encrypt the data provided by the user and store them as SharedPreferences | + | |
- | * Initialize the Cipher with the following algorithm/mode/padding string: "RSA/ECB/PKCS1Padding" | + | |
- | ==== Task 4 - Decryption (2.5p) ==== | + | ==== Task 6 - Sign the application (1p) ==== |
- | * When the user presses the "Show pin" button load the data from SharedPreferences and decrypt it | + | |
- | ==== Task 5 (2p - optional) ==== | + | In this task will see how Android applications are signed: |
- | * Save more than one description & pin pair and show them in a list. | + | |
+ | * Create a new application (with an empty activity). | ||
+ | * Using the steps presented in the lab create a key and keystore and generate the signed APK of the application. | ||
+ | * Use adb tool to install the APK. | ||
+ | |||
+ | ==== Task 7 - Signing configuration (1p) ==== | ||
+ | |||
+ | In this task will create a signing configuration for different build types. | ||
+ | - Create a release signing configuration with the data from Task 1. [[https://developer.android.com/studio/publish/app-signing#sign-auto|Auto sign]] | ||
+ | - Build a release version when pressing Run. Check **Build** -> **Select Build Variant** view from Android Studio. | ||
+ | - Create a release signing configuration using a new key. | ||
+ | - Sign the application wth the new release config and use adb to install the signed apk. You should install the new signed apk on top of the one from item #2. What happens with the application? | ||
+ | |||
+ | |||
+ | ==== Task 8 - Generate and Verify HMAC (2p) ==== | ||
+ | |||
+ | <note tip> | ||
+ | Hash-based message authentication code (HMAC) is a mechanism for verifying the authenticity and integrity of a message. | ||
+ | You can compute it using a hashing crypto algorithm (e.g. SHA-2 family HMAC) and a secret symmetric key. In Android you can use the standard Java API (javax.crypto) for computing it. | ||
+ | </note> | ||
+ | |||
+ | Add an activity to the project. Include an **EditText** and a **Button** in the first activity. When the user types a text and presses the button, it will send the text to the second activity through an intent (**putExtra**). In the second activity, get the message from the Intent and display it in the **TextView**. | ||
+ | |||
+ | In the first activity generate a symmetric key using [[https://developer.android.com/reference/javax/crypto/KeyGenerator|KeyGenerator]] for //HmacSha256// algorithm. Save this key in a Singleton (that can be accessed from both activities). Then generate the HMAC of the text introduced by the user (using [[https://developer.android.com/reference/javax/crypto/Mac.html|MAC]] with HmacSha256 algorithm) and send the HMAC along with the initial message (through the Intent). In the second activity, obtain the HMAC from the Intent, obtain the Singleton, get the symmetric key and recompute the HMAC. If the HMAC is valid (equal with the recomputed one), Display the message "Data is unmodified". | ||
+ | |||
+ | Send the data and HMAC as byte arrays in the Intent. Use Arrays.equals() for byte arrays comparison. | ||
+ | |||
+ | <code Java> | ||
+ | String secret = "secret"; | ||
+ | String message = "important message"; | ||
+ | |||
+ | Mac sha256HMAC = Mac.getInstance("HmacSHA256"); | ||
+ | SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(),"HmacSHA256"); | ||
+ | sha256HMAC.init(secretkey); | ||
+ | byte[] secretMessageBytes = sha256HMAC.doFinal(message.getBytes()) | ||
+ | </code> | ||
- | :!: If you generate the keys with a certain parameter configuration and then you change it, you need to clear the application's data in order for the encryption/decryption to work because you reuse the keys already existing in the keystore. You can also uninstall the app in order to clear the data. Running the app from the android studio's run button won't clear the app's data. | ||
===== Resources and useful links ===== | ===== Resources and useful links ===== | ||
+ | * Optional code skeleton: {{:smd:laboratoare:lab7-skel.zip|archive}} [[https://github.com/SMD-UPB/labs/tree/main/lab7/skel|github]] | ||
* [[https://developer.android.com/guide/topics/security/cryptography| Android Cryptography Guide]] | * [[https://developer.android.com/guide/topics/security/cryptography| Android Cryptography Guide]] | ||
* [[https://developer.android.com/training/articles/keystore| Keystore tutorial]] | * [[https://developer.android.com/training/articles/keystore| Keystore tutorial]] | ||
+ | * [[https://developer.android.com/topic/security/data|Jetpack Security library]] - new library, in beta | ||
+ | * [[https://proandroiddev.com/encrypted-preferences-in-android-af57a89af7c8|EncryptedSharedPreferences example]] | ||
* [[https://proandroiddev.com/secure-data-in-android-encryption-in-android-part-1-e5fd150e316f|Secure data in Android — Encryption in Android (Part 1)]] | * [[https://proandroiddev.com/secure-data-in-android-encryption-in-android-part-1-e5fd150e316f|Secure data in Android — Encryption in Android (Part 1)]] | ||
- | * [[smd:cursuri:05|Lecture 5 - Cryptographic Providers Section]] | + | * [[smd:cursuri:android-crypto|Lecture 5 - Cryptography and Network Security]] |
+ | * [[https://proandroiddev.com/security-best-practices-symmetric-encryption-with-aes-in-java-7616beaaade9|Security Best Practices: Symmetric Encryption with AES in Java and Android]] | ||