Basic Definitions
The goal of this chapter is to define used functions related to cryptography and data manipulation. The definitions crafted in this chapter are then used in pseudo-codes in documentation. You can learn more about actual implementation of following functions in the “Implementation Notes” section.
Cryptographic Functions
The following basic cryptography algorithms and parameters are used in the PowerAuth cryptography description:
AES Symmetric Encryption
A symmetric key encryption algorithm, uses CBC mode of operation. It defines the following methods:
Encryption
Encrypt bytes using symmetric key with given initialization vector and AES/CBC/PKCS7Padding
transformation:
byte[] encrypted = AES.encrypt(byte[] original, byte[] iv, SecretKey key);
Encrypt bytes using symmetric key with given initialization vector and given cipher transformation.
byte[] encrypted = AES.encrypt(byte[] original, byte[] iv, SecretKey key, String transformation);
Decryption
Decrypt bytes using symmetric key with given initialization vector and AES/CBC/PKCS7Padding
transformation:
byte[] original = AES.decrypt(byte[] encrypted, byte[] iv, SecretKey key);
Decrypt bytes using symmetric key with given initialization vector and given cipher transformation.
byte[] original = AES.decrypt(byte[] encrypted, byte[] iv, SecretKey key, String transformation);
PBKDF2
An algorithm for key stretching, converts a short password into long key by performing repeated hash iteration on the original data. HMAC-SHA1 algorithm is used for a pseudo-random function. Implementations must make sure resulting key is converted into a format usable by the AES algorithm.
The following method will stretch the password using given number of iterations to achieve key of given length in bits. Use a provided salt value.
SecretKey expandedKey = PBKDF2.expand(char[] password, byte[] salt, long iterations, long lengthInBits);
X9.63 KDF with SHA256
A standard KDF function based on X9.63, with SHA256 as an internal hash function. It uses iterations of SHA256 hash function to derive a key of expected length
of bytes.
Use the following method to derive a key of expected length from original secret value, using additional info byte value.
byte[] bytes = KDF_X9_63_SHA256.derive(byte[] secret, byte[] info, int length);
ECDSA Signatures
An algorithm for elliptic curve based signatures, uses SHA256 hash algorithm and P256r1 EC curve. It defines the following operations:
Data Signing
Compute signature of given data with a private key.
byte[] signature = ECDSA.sign(byte[] data, PrivateKey privateKey);
Signature Verification
Verify the signature for given data using a given public key.
boolean isValid = ECDSA.verify(byte[] data, byte[] signature, PublicKey publicKey);
ECDH Key Agreement
An algorithm for elliptic curve Diffie-Hellman, uses P256r1 curve. We define a single operation on ECDH, a symmetric key deduction between parties A and B:
Derive a shared secret using a private key of party A and a public key of party B using the follwing method.
SecretKey secretKey = ECDH.phase(PrivateKey privateKeyA, PublicKey publicKeyB);
KDF
A key derivation function used to derive a symmetric key with specific “index” from a given master key. Uses AES
algorithm with the zero initialization vector to derive the new key in following way: index
is converted to bytes, XORed with a 16 byte long zero array (to get 16 byte long array with bytes from the index) and AES encrypted using provided symmetric key masterKey
.
To obtain a key derived from a master key using a provided index, use:
SecretKey derivedKey = KDF.derive(SecretKey masterKey, long index);
KDF_INTERNAL
A second key derivation function for the algorithm internal purposes used to derive a symmetric key with specific “index” (in this case, byte[16]
) from a given master key. Uses HMAC-SHA256
to derive the new key in following way:
The index
used as HMAC-SHA256
key, provided symmetric key masterKey
is converted to key bytes used as HMAC-SHA256
data, resulting 32B long byte array is then XORed on per-byte basis (0th with 16th, 1st with 17th, etc.) to obtain 16B long byte array.
To obtain a key derived from a master key using a provided index, use:
SecretKey derivedKey = KDF_INTERNAL.derive(SecretKey masterKey, byte[] index);
Helper Functions
These functions are used in the pseudo-codes:
Key Generators.
Generate Random Key Pair
Generate a new EC key pair for the P256r1 elliptic curve.
KeyPair keyPair = KeyGenerator.randomKeyPair();
Key Conversion Utilities.
Convert Private Key to Bytes
Get bytes from the EC private key by encoding the D value (the number defining the EC private key).
byte[] privateKeyBytes = KeyConversion.getBytes(PrivateKey privKey)
Convert Bytes to Private Key
Get EC key pair private key by decoding the bytes into the original D value (the number defining the EC private key).
PrivateKey privateKey = KeyConversion.privateKeyFromBytes(byte[] privKeyBytes);
Convert Public Key to Bytes
Get bytes from the EC public key by encoding the Q value (the point defining the EC public key).
byte[] publicKeyBytes = KeyConversion.getBytes(PublicKey pubKey);
Convert Bytes to Public Key
Get EC public key by decoding the bytes into the original Q value (the point defining the EC public key).
PublicKey publicKey = KeyConversion.publicKeyFromBytes(byte[] pubKeyBytes);
Convert Secret Key to Bytes
Get bytes from the symmetric key (using the getEncoded
method).
byte[] secretKeyBytes = KeyConversion.getBytes(SecretKey secretKey);
Convert Bytes to Secret Key
Create a symmetric key using provided bytes.
SecretKey secretKey = KeyConversion.secretKeyFromBytes(byte[] secretKeyBytes);
Random Data Generators.
Generate Random Data
Generate N random bytes using a secure random generator.
byte[] randomBytes = Generator.randomBytes(int N);
Generate Random Base32 String
Generate string in Base32 encoding with N characters using a secure random generator.
String randomBase32 = Generator.randomBase32String(int N);
Generate Random UUID
Generate a new UUID level 4 and return it in string representation.
String uuid = Generator.randomUUID();
Generate Random Activation Code
Generate a new ACTIVATION_CODE
. See Activation Code for more details.
String code = Generator.randomActivationCode();
Build Activation Code With Random Bytes
Function return an activation code from given random data.
String code = Generator.buildActivationCode(byte[10] randomBytes)
MAC Functions
HMAC-SHA256
Compute HMAC-SHA256 signature for given message using provided symmetric key.
byte[] signature = Mac.hmacSha256(SecretKey key, byte[] message);
Hashing Functions.
SHA256
Compute SHA256 hash of a given input.
byte[] hash = Hash.sha256(byte[] original);
Password Hashing
Compute Password Hash
Compute Argon2 hash for given password. Hash is stored in Modular Crypt Format.
String hash = PasswordHash.hash(byte[] password);
Verify Password Hash
Verify password against Argon2 hash stored in Modular Crypt Format.
boolean matches = PasswordHash.verify(byte[] password, String hash);
Utility Functions.
Obtain Zero Byte Array
Generate buffer with N zero bytes.
byte[] zeroBytes = ByteUtils.zeroBytes(int N);
Truncate Array
Get last N bytes of given byte array.
byte[] truncatedBytes = ByteUtils.truncate(byte[] bytes, int N);
Get Numbers From Byte Array
Get integer value from big endian encoded byte array.
int integer = ByteUtils.getInt(byte[4] bytes);
Get long value from big endian encoded byte array.
long value = ByteUtils.getLong(byte[8] bytes);
Encode Primitive Types To Byte Array
Encode short value into byte array in big endian order.
byte[] encoded = ByteUtils.encode(short n);
Encode int value into byte array in big endian order.
byte[] encoded = ByteUtils.encode(int n);
Encode long value into byte array in big endian order.
byte[] encoded = ByteUtils.encode(long n);
Encode string into sequence of bytes with UTF-8 encoding.
byte[] encoded = ByteUtils.encode(String s);
Concatenate Data
Concatenate two or more byte arrays.
byte[] ByteUtils.concat(byte[]... args) {
byte[] result = new byte[0];
for (byte[] component : args) {
if (component != null && component.length > 0) {
byte[] tmp = new byte[result.length + component.length];
ByteUtils.copy(result, 0, tmp, 0, result.length);
ByteUtils.copy(component, 0, tmp, result.length, component.length);
result = tmp;
}
}
return result;
}
Concatenate multiple byte array elements and prepend the length of each element.
byte[] ByteUtils.concatWithSizes(byte[]... args) {
byte[] result = new byte[0];
for (byte[] component : args) {
if (component != null) {
result = ByteUtils.concat(result, ByteUtils.encode(component.length), component);
} else {
result = ByteUtils.concat(result, ByteUtils.encode((int)0));
}
}
return result;
}
Concatenate multiple string elements and prepend the length of each element.
byte[] ByteUtils.concatWithSizes(String... args) {
byte[] result = new byte[0];
for (String component : args) {
if (component != null) {
byte[] componentBytes = ByteUtils.encode(component);
result = ByteUtils.concat(result, ByteUtils.encode(componentBytes.length), componentBytes);
} else {
result = ByteUtils.concat(result, ByteUtils.encode((int)0));
}
}
return result;
}
Convert 32b Array to 16b
Converts 32b long byte array to 16b long array by xor-ing the first 16b with the second 16b, byte-by-byte.
byte[] result = ByteUtils.convert32Bto16B(byte[] bytes32);
Obtain Sub-Array
Obtain subarray of a byte array, starting with index startIndex
with a given length.
byte[] result = ByteUtils.subarray(byte[] bytes, int startIndex, int length);
Copy Arrays
Copies length
of bytes from the specified source array of bytes, beginning at the specified position, to the specified position of the destination array.
ByteUtils.copy(byte[] source, int sourcePosition, byte[] destination, int destinationPosition, int length);
Current Time
Get UNIX timestamp in milliseconds, since 1.1.1970.
long timestamp = Time.getTimestamp();