Understanding the EncryptionKeyGenerator class



It isn’t necessary to understand the inner workings of the EncryptionKeyGenerator class to use it to create a secure encryption key for your application database. The process for using the class is explained in Using the EncryptionKeyGenerator class to obtain a secure encryption key. However, you might find it valuable to understand the techniques that the class uses. For example, you might want to adapt the class or incorporate some of its techniques for situations where a different level of data privacy is desired.

The EncryptionKeyGenerator class is included in the open-source ActionScript 3.0 core library (as3corelib) project. You can download the as3corelib package including source code and documentation.You can also view the source code on the project site or download it to follow along with the explanations.

When code creates an EncryptionKeyGenerator instance and calls its getEncryptionKey() method, several steps are taken to ensure that only the rightful user can access the data. The process is the same to generate an encryption key from a user-entered password before the database is created as well as to re-create the encryption key to open the database.

Obtain and validate a strong password

When code calls the getEncryptionKey() method, it passes in a password as a parameter. The password is used as the basis for the encryption key. By using a piece of information that only the user knows, this design ensures that only the user who knows the password can access the data in the database. Even if an attacker accesses the user’s account on the computer, the attacker can’t get into the database without knowing the password. For maximum security, the application never stores the password.

In the example application code, passwordInput is the ID of a TextInput component in which the user enters the password. Rather than manipulating the component’s text value directly, the application copies the password into a variable named password.

var password:String = passwordInput.text;

The example application then creates an EncryptionKeyGenerator instance and calls its getEncryptionKey() method, using the password variable as an argument:

var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator(); 
var encryptionKey:ByteArray = keyGenerator.getEncryptionKey(password);

The first step the EncryptionKeyGenerator class takes when the getEncryptionKey() method is called is to check the user-entered password to ensure that it meets the password strength requirements. In this case the password must be 8 - 32 characters long. It must contain a mix of uppercase and lowercase letters and at least one number or symbol character.

The regular expression that checks this pattern is defined as a constant named STRONG_PASSWORD_PATTERN:

private static const STRONG_PASSWORD_PATTERN:RegExp = /(?=^.{8,32}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/;

The code that checks the password is in the EncryptionKeyGenerator class’s validateStrongPassword() method. The code is as follows:

public function vaidateStrongPassword(password:String):Boolean 
{ 
    if (password == null || password.length <= 0) 
    { 
        return false; 
    } 
     
    return STRONG_PASSWORD_PATTERN.test(password)) 
}

The getEncryptionKey() method calls the validateStrongPassword() method and, if the password isn’t valid, throws an exception. The validateStrongPassword() method is a public method so that code can check a password without calling the getEncryptionKey() method to avoid causing an error.

Expand the password to 256 bits

Later in the process, the password is required to be 256 bits long. Rather than require each user to enter a password that’s exactly 256 bits (32 characters) long, the code creates a longer password by repeating the password characters.

The getEncryptionKey() method calls the concatenatePassword() method to perform the task of creating the long password.

var concatenatedPassword:String = concatenatePassword(password);

The following is the code for the concatenatePassword() method:

private function concatenatePassword(pwd:String):String 
{ 
    var len:int = pwd.length; 
    var targetLength:int = 32; 
     
    if (len == targetLength) 
    { 
        return pwd; 
    } 
     
    var repetitions:int = Math.floor(targetLength / len); 
    var excess:int = targetLength % len; 
     
    var result:String = ""; 
     
    for (var i:uint = 0; i < repetitions; i++) 
    { 
        result += pwd; 
    } 
     
    result += pwd.substr(0, excess); 
     
    return result; 
}

If the password is less than 256 bits, the code concatenates the password with itself to make it 256 bits. If the length doesn’t work out exactly, the last repetition is shortened to get exactly 256 bits.

Generate or retrieve a 256-bit salt value

The next step is to get a 256-bit salt value that in a later step is combined with the password. A salt is a random value that is added to or combined with a user-entered value to form a password. Using a salt with a password ensures that even if a user chooses a real word or common term as a password, the password-plus-salt combination that the system uses is a random value. This randomness helps guard against a dictionary attack, where an attacker uses a list of words to attempt to guess a password. In addition, by generating the salt value and storing it in the encrypted local store, it is tied to the user’s account on the machine on which the database file is located.

If the application is calling the getEncryptionKey() method for the first time, the code creates a random 256-bit salt value. Otherwise, the code loads the salt value from the encrypted local store.

The salt is stored in a variable named salt. The code determines if it’s already created a salt by attempting to load the salt from the encrypted local store:

var salt:ByteArray = EncryptedLocalStore.getItem(saltKey); 
if (salt == null) 
{ 
    salt = makeSalt(); 
    EncryptedLocalStore.setItem(saltKey, salt); 
}

If the code is creating a new salt value, the makeSalt() method generates a 256-bit random value. Because the value is eventually stored in the encrypted local store, it is generated as a ByteArray object. The makeSalt() method uses the Math.random() method to randomly generate the value. The Math.random() method can’t generate 256 bits at one time. Instead, the code uses a loop to call Math.random() eight times. Each time, a random uint value between 0 and 4294967295 (the maximum uint value) is generated. A uint value is used for convenience, because a uint uses exactly 32 bits. By writing eight uint values into the ByteArray, a 256-bit value is generated. The following is the code for the makeSalt() method:

private function makeSalt():ByteArray 
{ 
    var result:ByteArray = new ByteArray; 
     
    for (var i:uint = 0; i < 8; i++) 
    { 
        result.writeUnsignedInt(Math.round(Math.random() * uint.MAX_VALUE)); 
    } 
     
    return result; 
}

When the code is saving the salt to the Encrypted Local Store (ELS) or retrieving the salt from the ELS, it needs a String key under which the salt is saved. Without knowing the key, the salt value can’t be retrieved. In that case, the encryption key can’t be re-created each time to reopen the database. By default, the EncryptionKeyGenerator uses a predefined ELS key that is defined in the constant SALT_ELS_KEY. Instead of using the default key, application code can also specify an ELS key to use in the call to the getEncryptionKey() method. Either the default or the application-specified salt ELS key is stored in a variable named saltKey. That variable is used in the calls to EncryptedLocalStore.setItem() and EncryptedLocalStore.getItem(), as shown in a previous code listing.

Combine the 256-bit password and salt using the XOR operator

The code now has a 256-bit password and a 256-bit salt value. It next uses a bitwise XOR operation to combine the salt and the concatenated password into a single value. In effect, this technique creates a 256-bit password consisting of characters from the entire range of possible characters. This principle is true even though most likely the actual password input consists of primarily alphanumeric characters. This increased randomness provides the benefit of making the set of possible passwords large without requiring the user to enter a long complex password.

The result of the XOR operation is stored in the variable unhashedKey. The actual process of performing a bitwise XOR on the two values happens in the xorBytes() method:

var unhashedKey:ByteArray = xorBytes(concatenatedPassword, salt);

The bitwise XOR operator (^) takes two uint values and returns a uint value. (A uint value contains 32 bits.) The input values passed as arguments to the xorBytes() method are a String (the password) and a ByteArray (the salt). Consequently, the code uses a loop to extract 32 bits at a time from each input to combine using the XOR operator.

private function xorBytes(passwordString:String, salt:ByteArray):ByteArray 
{ 
    var result:ByteArray = new ByteArray(); 
     
    for (var i:uint = 0; i < 32; i += 4) 
    { 
        // ... 
    } 
     
    return result; 
}

Within the loop, first 32 bits (4 bytes) are extracted from the passwordString parameter. Those bits are extracted and converted into a uint (o1) in a two-part process. First, the charCodeAt() method gets each character’s numeric value. Next, that value is shifted to the appropriate position in the uint using the bitwise left shift operator (<<) and the shifted value is added to o1. For example, the first character (i) becomes the first 8 bits by using the bitwise left shift operator (<<) to shift the bits left by 24 bits and assigning that value to o1. The second character (i + 1) becomes the second 8 bits by shifting its value left 16 bits and adding the result to o1. The third and fourth characters’ values are added the same way.

        // ... 
         
        // Extract 4 bytes from the password string and convert to a uint 
        var o1:uint = passwordString.charCodeAt(i) << 24; 
        o1 += passwordString.charCodeAt(i + 1) << 16; 
        o1 += passwordString.charCodeAt(i + 2) << 8; 
        o1 += passwordString.charCodeAt(i + 3); 
         
        // ...

The variable o1 now contains 32 bits from the passwordString parameter. Next, 32 bits are extracted from the salt parameter by calling its readUnsignedInt() method. The 32 bits are stored in the uint variable o2.

        // ... 
         
        salt.position = i; 
        var o2:uint = salt.readUnsignedInt(); 
         
        // ...

Finally, the two 32-bit (uint) values are combined using the XOR operator and the result is written into a ByteArray named result.

        // ... 
         
        var xor:uint = o1 ^ o2; 
        result.writeUnsignedInt(xor); 
        // ...

Once the loop completes, the ByteArray containing the XOR result is returned.

        // ... 
    } 
     
    return result; 
}

Hash the key

Once the concatenated password and the salt have been combined, the next step is to further secure this value by hashing it using the SHA-256 hashing algorithm. Hashing the value makes it more difficult for an attacker to reverse-engineer it.

At this point the code has a ByteArray named unhashedKey containing the concatenated password combined with the salt. The ActionScript 3.0 core library (as3corelib) project includes a SHA256 class in the com.adobe.crypto package. The SHA256.hashBytes() method that performs a SHA-256 hash on a ByteArray and returns a String containing the 256-bit hash result as a hexadecimal number. The EncryptionKeyGenerator class uses the SHA256 class to hash the key:

var hashedKey:String = SHA256.hashBytes(unhashedKey);

Extract the encryption key from the hash

The encryption key must be a ByteArray that is exactly 16 bytes (128 bits) long. The result of the SHA-256 hashing algorithm is always 256 bits long. Consequently, the final step is to select 128 bits from the hashed result to use as the actual encryption key.

In the EncryptionKeyGenerator class, the code reduces the key to 128 bits by calling the generateEncryptionKey() method. It then returns that method’s result as the result of the getEncryptionKey() method:

var encryptionKey:ByteArray = generateEncryptionKey(hashedKey); 
return encryptionKey;

It isn’t necessary to use the first 128 bits as the encryption key. You could select a range of bits starting at some arbitrary point, you could select every other bit, or use some other way of selecting bits. The important thing is that the code selects 128 distinct bits, and that the same 128 bits are used each time.

In this case, the generateEncryptionKey() method uses the range of bits starting at the 18th byte as the encryption key. As mentioned previously, the SHA256 class returns a String containing a 256-bit hash as a hexadecimal number. A single block of 128 bits has too many bytes to add to a ByteArray at one time. Consequently, the code uses a for loop to extract characters from the hexadecimal String, convert them to actual numeric values, and add them to the ByteArray. The SHA-256 result String is 64 characters long. A range of 128 bits equals 32 characters in the String, and each character represents 4 bits. The smallest increment of data you can add to a ByteArray is one byte (8 bits), which is equivalent to two characters in the hash String. Consequently, the loop counts from 0 to 31 (32 characters) in increments of 2 characters.

Within the loop, the code first determines the starting position for the current pair of characters. Since the desired range starts at the character at index position 17 (the 18th byte), the position variable is assigned the current iterator value (i) plus 17. The code uses the String object’s substr() method to extract the two characters at the current position. Those characters are stored in the variable hex. Next, the code uses the parseInt() method to convert the hex String to a decimal integer value. It stores that value in the int variable byte. Finally, the code adds the value in byte to the result ByteArray using its writeByte() method. When the loop finishes, the result ByteArray contains 16 bytes and is ready to use as a database encryption key.

private function generateEncryptionKey(hash:String):ByteArray 
{ 
    var result:ByteArray = new ByteArray(); 
     
    for (var i:uint = 0; i < 32; i += 2) 
    { 
        var position:uint = i + 17; 
        var hex:String = hash.substr(position, 2); 
        var byte:int = parseInt(hex, 16); 
        result.writeByte(byte); 
    } 
     
    return result; 
}