قالب وردپرس درنا توس
Home / Apple / Encryption tutorial for Android: Getting started

Encryption tutorial for Android: Getting started



With all the latest data breaches and the new privacy law for example GDPR the app's credibility depends on how to manage the user's data. There are powerful Android APIs that focus on data encryption that is sometimes overlooked when starting a project. You can put them to great use and think about safety from the ground up.

In this guide, you will ensure an app for veterinary clinics that stores medical information. During the process you will learn how to:

  • Tighten app permissions
  • Encrypt your data
  • Use KeyStore

Note : This tutorial assumes that you are already familiar with the basics of Android development and Android Studio. If Android development is new to you, you first need to read through Start Android Development and Kotlin for the Android tutorial.

Getting Started

Download the startup project by clicking the Download Materials button at the top or bottom of this tutorial. Take the time to familiarize yourself with the project's structure. Build and run the app to see what you're working on.

You will see a simple login screen. After entering a password and selecting Registration you are prompted for that password at subsequent app launches. After that step you get a list of pets. Most of the app is complete, so you will focus on securing it. Click on an entry in the list to reveal the pet's medical information:

 Pet Details Screen

If you get a crash with errors on Android 7+, you will get an error java. lang.SecurityException: MODE_WORLD_READABLE no longer supported do not worry. You need to fix it soon.

Securing the Foundation

To begin encrypting your applications and securing important data, first prevent data leakage to the rest of the world . In the case of Android, this usually means protecting your user-based data from being read by any other program and limiting the location where the applications are installed. Let's do this first, so you can start encrypting private information.

Using Permissions

When you first start building your app, it is important to think about how much user data you actually need to keep. These days it's best to avoid storing private data if you don't need it – especially for our cute little Lightning, who is concerned about his privacy.

Already since Android 6.0, files and SharedPreferences ] you store are set with MODE_PRIVATE constant. That means only your app can access the data. Android 7 does not allow any other option. So first thing first, make sure the project is set up safely.

Open the file MainActivity.kt . You will notice that there are two depreciation warnings for MODE_WORLD_READABLE and MODE_WORLD_WRITABLE . These allow public access to your files on previous Android versions. Find the line that sets MODE_WORLD_WRITABLE and replace it with the following:

  select preferences = getSharedPreferences ("MyPrefs", Context.MODE_PRIVATE)

Then find the line that sets MODE_WORD_READABLE and replaces it with this:

  selection editor = getSharedPreferences ("MyPrefs", Context.MODE_PRIVATE) .edit ()

Great, you've just made your preferences a little safer! In addition, if you build and run the program now, you should not get the crash you previously experienced due to security breaches on Android 7+ versions. You should now enforce a secure location for your app install folder.

Installation Folder Limitation

One of the biggest problems that Android faces in recent years does not have enough memory to install many applications. This is mostly due to lower storage capacity on devices, but since the technology has advanced and the phones have become somewhat cheaper, most devices now pack plenty of storage for a multitude of apps. To reduce insufficient storage, Android allows you to install apps for external storage . This seemed pretty good, but over the years, many security issues have been raised around this approach. Installing applications on external SD cards is a cool way to save on storage, but also a security error, since anyone who has access to the SD card also has access to the application data. And that the data can contain sensitive information. This is why it is encouraged to limit your app to internal storage.

To do this, press AndroidManifest.xml file and find the line that reads android: installLocation = "auto" and replace it with the following:

  android: installLocation = "internal only"

Now, the installation site is limited to the device, but you can still back up your app and its data. This means that users can access the contents of the app's private data folder using ADB backup . To reject backups, find the line that reads android: allowBackup = "true" and replace the value with "false" .

Having followed these best practices, you have hardened your app to some extent. However, you can circumvent these permission measurements on a rooted device. The solution is to encrypt the data with a piece of information that potential attackers do not find.

Secure User Data with a Password

 Device Lock Screen

You encrypt the data with a known, recommended standard, Advanced Encryption Standard (AES) . AES uses substitution permutation network to encrypt your data with a key. Using this approach, it replaces bytes from one table with bytes from another, and as such creates permutations of data. To start using AES, you must first create the encryption key, so let's do it.

Creating a Key

As mentioned above, AES uses a key for encryption. The same key is also used to decrypt the data. This is called symmetric encryption . The key can be different lengths, but 256 bits is standard. Direct use of the user's password for encryption is dangerous. It probably won't be random or big enough. As such, the user password is different from the encryption key.

A function called Password-Based Key Derivation Function (PBKDF2) comes to the rescue. It takes a password, and by hashing it with random data many times over, it creates a key. The random data is called salt . This creates a strong and unique key, even if someone else uses the same password.

 PBKDF2 chart

Because each key is unique, if an attacker steals and publishes the key online, not all users who used the same password do not rule out.

Begin by generating the salt. Open the file Encryption.kt and add the following code in the first encryption method where it reads // TODO: Add code here :

 ] random choice = SecureRandom ()
fall salt = ByteArray (256)
random.nextBytes (salt)

Here you use the SecureRandom class, which ensures that the output is difficult to predict. It is called a cryptographic strong random number generator .

Now you generate a key with the user's password and the salt. Add the following directly below the code you just added:

  selection pbKeySpec = PBEKeySpec (password, salt, 1324, 256) // 1
fall secretKeyFactory = SecretKeyFactory.getInstance ("PBKDF2WithHmacSHA1") // 2
fall keyBytes = secretKeyFactory.generateSecret (pbKeySpec) .encoded // 3
select keySpec = SecretKeySpec (keyBytes, "AES") // 4

Here's what happens in that code. You:

  1. Insert salt and password into PBEKeySpec a password-based encryption object. The designer takes an iteration statement ( 1324 ). The higher the number, the longer it would take to operate on a set of keys during a brutal force strike.
  2. Passed PBEKeySpec into SecretKeyFactory .
  3. Generated the key as a ByteArray .
  4. Wrapped the raw ByteArray into a SecretKeySpec object.

Note : For the password, most of these functions work with a CharArray instead of String objects. That's because objects like String are immutable. A CharArray can be overwritten, allowing you to erase sensitive information from memory when you are done.

Adding an Initialization Vector

You are almost ready to encrypt the data, but there is one more thing to do.

AES works in different modes. Standard mode is called encryption block (CBC) . CBC encrypts your data a bit at a time.

CBC is safe because each block of data in the pipeline is XOR d with the previous block as it encrypted. This reliance on previous blocks makes encryption strong, but can you see a problem? What about the first block?

If you encrypt a message that starts immediately as another message, the first encrypted block will be the same! It gives a clue to an attacker. To correct this, use a initialization vector (IV) .

An IV is a fancy term for a block of random data that becomes XOR & d with the first block. Remember, each block is dependent on all the blocks that are processed up to that point. This means that identical sets of data encrypted with the same key will not produce equal outputs.

Create an IV now by adding the following code right after the code you just added:

  choice ivRandom = SecureRandom () // not caching previously seeded instance of SecureRandom
// 1
fall iv = ByteArray (16)
ivRandom.nextBytes (iv)
fall ivSpec = IvParameterSpec (iv) // 2

Here, you:

  1. Created 16 bytes of random data.
  2. Packed it into a IvParameterSpec object.

Note : On Android 4.3 and below, there was a vulnerability with SecureRandom . It had to do with incorrect initialization of the underlying pseudorandom number generator (PRNG). A solution is available if you support Android 4.3 and below.

Encryption of the data

 Encryption icon

Now that you have all the necessary tags, add the following code to perform the encryption:

  select cipher = Cipher.getInstance ("AES / CBC / PKCS7Padding ") // 1
cipher.init (Cipher.ENCRYPT_MODE, keySpec, ivSpec)
selection encrypted = cipher.doFinal (dataToEncrypt) // 2

Here:

  1. You passed in the specification string "AES / CBC / PKCS7Padding" . It selects AES with encryption blocking mode. PKCS7Padding is a well-known standard for padding. Since you work with blocks, not all data fits perfectly into the block size, so you have to throw the remaining space. By the way, the blocks are 128 bit long and AES adds upholstery before encryption.
  2. doFinal does the actual encryption.

Then add the following:

  map ["salt"] = salt
kart ["iv"] = iv
map ["encrypted"] = encrypted

You packed the encrypted data into a HashMap . You have also added the salt and initialization vector to the map. This is because all these parts are needed to decrypt the data.

If you followed the steps correctly, you should have no errors and the encryption feature is ready to secure any data! It is OK to store salts and IVs, but reusing or sequentially increasing them weakens safety. But you should never save the key! Right now you have built the way to encrypt data, but to read it later, you still need to decrypt it. Let's see how you do it.

Decrypt the data

 Decryption icon

Now you have some encrypted data. To decrypt it, you must change the mode of Cipher

 in the  init  method from  ENCRYPT_MODE  to  DECRYPT_MODE . Add the following in  decrypt the  method in the file  Encryption.kt  where the line reads  // TODO: Add code here : 

  // 1
fall salt = map ["salt"]
choice iv = map ["iv"]
selection encrypted = map ["encrypted"]

// 2
// regenerate key from password
choice pbKeySpec = PBEKeySpec (password, salt, 1324, 256)
select secretKeyFactory = SecretKeyFactory.getInstance ("PBKDF2WithHmacSHA1")
select keyBytes = secretKeyFactory.generateSecret (pbKeySpec) .coded
select keySpec = SecretKeySpec (keyBytes, "AES")

// 3
// Decrypt
drop cipher = Cipher.getInstance ("AES / CBC / PKCS7Padding")
fall ivSpec = IvParameterSpec (iv)
cipher.init (Cipher.DECRYPT_MODE, keySpec, ivSpec)
decrypted = cipher.doFinal (encrypted)

In this code you did the following:

  1. Used HashMap containing encrypted data, salt and IV needed for decryption.
  2. Regenerated the key given the information as well as the user's password
  3. Decrypted the data and returned it as a ByteArray .

Notice how you used the same decryption configuration, but have tracked your steps back. This is because you use a symmetric encryption algorithm. You can now encrypt data as well as decrypt it!

Oh, and I never mentioned saving the key? :]

Storing encrypted data

Now that the encryption process is complete, you need to save the data. The app already reads and writes data for storage. You update these methods to make it work with encrypted data.

In the file MainActivity.kt everything in is replaced by createDataSource method with this:

  selection inputStream = applicationContext.assets.open (file name)
bytes = inputStream.readBytes ()
inputStream.close ()

fall password = CharArray (login_password.length ())
login_password.text.getChars (0, login_password.length (), password, 0)
fall folder = Encryption (). encrypt (bytes, passwords)
ObjectOutputStream (FileOutputStream (outFile)). Use {
it -> it.writeObject (map)
}

In the updated code, you opened the data file as an input stream and fed the data into the encryption method. You serialized HashMap using ObjectOutputStream class and then saved it to storage.

Build and run the app. Note that the pets are now missing from the list:

 Clear list

 Keys

That's because the stored data is encrypted. You need to update the code to read the encrypted content. In the loadPets method of the file PetViewModel.kt remove the comment marker / * and * / and * / . Then add the following code where it reads // TODO: Add decryption calls here :

  decrypted = Encryption (). Decrypt (
hashMapOf ("iv" to iv, "salt" to salt, "encrypted" to encrypted), password)

You called the decryption method using encrypted data, IV and salt. Now that the input stream comes from a ByteArray instead of File replace the line reading selection inputStream = file.inputStream () with this:

 ] select inputStream = ByteArrayInputStream (decrypted)

If you build and run the program now, you should see a couple of friendly faces!

The data is now secure, but there is another common place for user data to be stored on Android, SharedPreferences that you have already used.

Securing SharedPreferences

The app also keeps track of the latest access time in SharedPreferences so there is another place in the app to protect. Saving sensitive information in SharedPreferences may be uncertain because you can still leak the information from your app even with Context.MODE_PRIVATE flag. You will fix it a little.

Open the file MainActivity.kt and replace saveLastLoggedInTime method with this code:

  // Get password
fall password = CharArray (login_password.length ())
login_password.text.getChars (0, login_password.length (), password, 0)

// Base64 data
select currentDateTimeString = DateFormat.getDateTimeInstance (). format (Date ())
// 1
fall map =
Encryption () .crypt (currentDateTimeString.toByteArray (Charsets.UTF_8), password)
// 2
drop valueBase64String = Base64.encodeToString (map ["encrypted"] Base64.NO_WRAP)
select saltBase64String = Base64.encodeToString (kart ["salt"] Base64.NO_WRAP)
fall ivBase64String = Base64.encodeToString (kart ["iv"] Base64.NO_WRAP)

// Save to shared prefs
fall editor = getSharedPreferences ("MyPrefs", Context.MODE_PRIVATE) .edit ()
// 3
editor.putString ("l", valueBase64String)
editor.putString ("lsalt", saltBase64String)
editor.putString ("life", ivBase64String)
editor.apply ()

Here, You:

  1. Converted String to a ByteArray with UTF-8 encoding and encrypted it. In the previous code, you opened a file as binary, but when it comes to working with strings, you need to pay attention to the character encoding.
  2. Converted Raw Data to a String representation. SharedPreferences cannot store a ByteArray directly, but it can work with String . Base64 is a standard that converts raw data to a string representation.
  3. Saved the strings to SharedPreferences . Alternatively, you can encrypt both the setting key and the value. That way, an attacker can't figure out what the value can be by looking at the key, and using keys as a "password" won't work for brutal force, as it will also be encrypted.

Now, replace lastLoggedIn method to get the encrypted swaps back:

  // Get passwords
fall password = CharArray (login_password.length ())
login_password.text.getChars (0, login_password.length (), password, 0)

// Retrieve shared prefs data
// 1
preference preferences = getSharedPreferences ("MyPrefs", Context.MODE_PRIVATE)
select base64Encrypted = preferences.getString ("l", "")
choice base64Salt = preferences.getString ("lsalt", "")
choice base64Iv = preferences.getString ("life", "")

// Base64 decoder
// 2
fall encrypted = Base64.decode (base64Encrypted, Base64.NO_WRAP)
fall iv = Base64.decode (base64Iv, Base64.NO_WRAP)
select salt = Base64.decode (base64Salt, Base64.NO_WRAP)

// Decrypt
// 3
choice decrypted = Encryption (). decrypt (
hashMapOf ("iv" to iv, "salt" to salt, "encrypted" to encrypted), password)

was lastLoggedIn: String? = zero
decrypted? .let {
lastLoggedIn = String (it, Charsets.UTF_8)
}
return lastLoggedIn

You did the following:

  1. Get the string representations for encrypted data, IV and salt.
  2. Used a Base64 decoder on the strings to convert them back to raw bytes. Passed the data into a HashMap to decrypt the method.

Now that you have storage space set up securely, restart by navigating to Settings 19 Apps Apps 19 PetMed 2 ] Storage 19 Remove data .

Build and run the app. If everything worked, after logging in, you should see your pet back on the screen. Esther is happy that her private data is encrypted.

 Pet list screen

Using a key from a server

You have just completed a great example of reality, but many apps require a good boarding experience. . Showing a password screen in addition to a login screen may not be a good user experience. For requirements like this, you have some options.

The first is to utilize a login password to derive the key. You can also get the server to generate that key instead. The key will be unique and securely transmitted when the user has authenticated with their credentials.

If you go on the server route, it is important to know that since the server generates the key, it has the capacity to decrypt data stored on the device. There is potential for someone to leak the key.

If none of these solutions work for you, you can take advantage of device security to secure the app.

Using KeyStore

Android M introduced the ability to work with an AES key using KeyStore API . This has some added benefits. KeyStore lets you operate on a key without revealing the secret content. Only the object and not the private data is available from the app slot.

Generating a New Random Key

 Keys

In the file Encryption.kt Add the following code in the keystoreTest method to generate a random key. This time, KeyStore protects the key:

  select keyGenerator = KeyGenerator.getInstance (KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") // 1
select keyGenParameterSpec = KeyGenParameterSpec.Builder ("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes (KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings (KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) // 2 requires lock screen, invalid if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) // 3 only available x seconds from password authentication. -1 requires fingerprints - every time
.setRandomizedEncryptionRequired (true) // 4 different ciphertext for the same text on each conversation
.build()
keyGenerator.init (keyGenParameterSpec)
keyGenerator.generateKey ()

Here is what goes on inside the code:

  1. You created a KeyGenerator example and set it to "AndroidKeyStore" .
  2. Alternatively,
  3. added .setUserAuthenticationValidityDurationSeconds (120) so that the key is available 120 seconds after device approval.
  4. You called .setRandomizedEncryptionRequired (true) . This tells KeyStore to use a new IV every time. As you have learned in the past, it means that if you encrypt identical data again, encrypted output will not be identical.

There are some other things about the KeyStore options you should know about:

  1. For .setUserAuthenticationValidityDurationSeconds () There are some other things about the KeyStore options you should know about:

    1. For .setUserAuthenticationValidityDurationSeconds () you can pass -1 to require fingerprint authentication each time you want to access the key.
    2. Activation of the screen lock requirements will revoke the keys as soon as the user removes or changes the lock screen's PIN or password.
    3. Storing a key in the same location as encrypted data is like putting a key under the doormat. KeyStore tries to protect the key with strict permissions and core code. On some devices, keys are hardware supported.
    4. You can use .setUserAuthenticationValidWhileOnBody (boolean remainsValid) . This makes the key unavailable when the device has detected that it is no longer on the person.

    Encryption of the data

    Now you will use the key stored in KeyStore. In the file Encryption.kt add the following in the keystoreEncrypt method, just below // TODO: Add code here :

      // 1
    // Get the key
    select keyStore = KeyStore.getInstance ("AndroidKeyStore")
    keyStore.load (null)
    
    select secretKeyEntry =
    keyStore.getEntry ("MyKeyAlias", null) as KeyStore.SecretKeyEntry
    fall secretKey = secretKeyEntry.secretKey
    
    // 2
    // Encrypt data
    Select cipher = Cipher.getInstance ("AES / GCM / NoPadding")
    cipher.init (Cipher.ENCRYPT_MODE, secretKey)
    drop ivBytes = cipher.iv
    select encryptedBytes = cipher.doFinal (dataToEncrypt)
    
    // 3
    map ["iv"] = ivBytes
    map ["encrypted"] = encrypted bytes
    

    Here:

    1. This time you get the key from KeyStore.
    2. You encrypted the data with Cipher object, given SecretKey .
    3. As before, you return a HashMap containing the encrypted data and IV needed to decrypt the data.

    Decrypt to a byte array

    Add the following in keystoreDecrypt right below // TODO: Add code here :

      // 1
    // Get the key
    select keyStore = KeyStore.getInstance ("AndroidKeyStore")
    keyStore.load (null)
    
    select secretKeyEntry =
    keyStore.getEntry ("MyKeyAlias", null) as KeyStore.SecretKeyEntry
    fall secretKey = secretKeyEntry.secretKey
    
    // 2
    // Extract info from map
    fall encryptedBytes = map ["encrypted"]
    fall ivBytes = map ["iv"]
    
    // 3
    // Decrypt data
    Select cipher = Cipher.getInstance ("AES / GCM / NoPadding")
    fall spec = GCMParameterSpec (128, ivBytes)
    cipher.init (Cipher.DECRYPT_MODE, secretKey, spec)
    decrypted = cipher.doFinal (encryptedBytes)
    

    In this code you are:

    1. Achieved the key again from KeyStore.
    2. Extracted necessary information from map .
    3. Initialized object Cipher using DECRYPT_MODE constantly decrypting the data into a ByteArray
      . 

    Test the example

    Now that you've created ways to encrypt and decrypt data using the KeyStore API, it's time to test them out. Add the following to the end of the keystoreTest method:

      // 1
    fall folder = keystoreEncrypt ("My very sensitive string!". toByteArray (Charsets.UTF_8))
    // 2
    choice decryptedBytes = keystoreDecrypt (map)
    decrypted bits? .let {
    choice decryptedString = String (it, Charsets.UTF_8)
    Log.e ("MyApp", "The decrypted string is: $ decryptedString")
    }
    

    In the updated code, you created:

    1. a test string and encrypted it. Called the decrypt method on encrypted output to test that everything worked. onCreate method in the file MainActivity.kt uncoment the line that reads // Encryption (). KeystoreTest () . Build and run the app to make sure it worked. You should see the decrypted string:

       Console Success Output

      Where to go from here?

      Congratulations on learning how to encrypt and decrypt data on Android!
        Happy Data

      You also learned other ways to work with keys using Keystore. You can download the final project using the Download Materials button at the top or bottom of this tutorial, if you skipped a few steps, to get the full work project and all the code filled out.

      It's great to know how to properly implement security. Armed with this knowledge, you will be able to confirm whether third-party security libraries are up to the best practices. However, everything itself can be accomplished, especially if it hurries, causing errors. If you are in that boat, consider using an industry approved or timed third party.

      Conceal is a good choice for a third-party encryption library. It makes you run without having to worry about the underlying details. One drawback - when hackers reveal a vulnerability in a popular library. This affects all the apps that rely on the third party at the same time. Apps that have a custom implementation are often immune to extensive spread attacks.

      The account administrator is part of the Android OS and has a similar API. It is a centralized user account credential manager, so your app does not need to store or work with passwords and log in directly. The best-known example of this is when you request a OAuth2 token .

      Introduced to Android 4.0 (API Level 14) addresses Keychain API key management. It works especially with PrivateKey and X509Certificate objects and provides a safer container than using the app's data storage. You can use it to install certificates and use private key objects directly.

      Your security code works well to protect your app as long as someone doesn't tamper with it. Take a look at the ProGuard tutorial to learn how to prevent reverse engineering or manipulate your security related code.

      Now that you've secured your data at rest, why not read about how to secure your data in transit? And for those of you looking for advanced cryptography, check out the GCM mode for AES.

      By the way, the sample data Esther, Cornelius, Lightning and Birgit are all real! :]

      If you have questions or comments on what you have learned, join the discussion forum below!




Source link