-
Notifications
You must be signed in to change notification settings - Fork 22
Implementation notes
PowerAuth Cryptography documentation has been moved to: https://developers.wultra.com/docs/develop/powerauth-crypto/Implementation-Notes
Please use the new developer portal to access documentation.
Following implementation notes use simplified Java code with definitions from the "Definitions" section or a simple pseudo-code to get the point across quicker.
A PowerAuth 2.0 key exchange mechanism is based on ECDH key exchange algorithm with P256r1 curve. Additionally, an ECDSA (more specifically, SHA256withECDSA algorighm) is used for signing data sent from the service provider using a provider's Master Private Key. After a successful key exchange, both client and server have a shared master secret and they establish a shared counter initialized on 0 (later on, each signature attempt increments this counter). The PowerAuth 2.0 signature is computed using data, shared master secret and counter using the HMAC algorithm.
KDF (Key Derivation Function) is an algorithm used for deriving a secret key from a master secret key using a pseudo-random function. PowerAuth uses three types of functions for KDF:
-
KDF - AES-based KDF function. Works by encrypting fixed
long
index with a random secret master key. This function is handy for situations where developer selects the function index. A human readable index, such as "1", "2", or "1000" can be selected for key derivation. -
KDF_INTERNAL - HMAC-SHA256-based KDF function. Works by computing HMAC-SHA256 of provided
byte[]
index with a random secret master key. This function is used in internal algorithm workings, in situations where readability of the index is not important at all. - PBKDF2 - Standard algorithm for deriving long keys from short passwords. This function is considered a standard KDF and it is used only for deriving base key from the user entered password. Since it has no major impact on PowerAuth cryptography, we will not elaborate about this KFD in more details.
public SecretKey kdfDeriveSecretKey(SecretKey secret, long index) {
byte[] bytes = ByteBuffer.allocate(16).putLong(index).array();
byte[] iv = new byte[16];
byte[] encryptedBytes = AES.encrypt(bytes, iv, secret);
byte[] newKeyBytes = ByteUtil.convert32Bto16B(derivedKey32);
return KeyConversion.secretKeyFromBytes(newKeyBytes);
}
public SecretKey deriveSecretKeyHmac(SecretKey secret, byte[] index) {
byte[] derivedKey = Mac.hmacSha256(secret, index);
byte[] newKeyBytes = ByteUtil.convert32Bto16B(derivedKey32);
return KeyConversion.secretKeyFromBytes(newKeyBytes);
}
For the purpose of completeness, the ByteUtil.convert32Bto16B()
function is implemented in a following manner:
public byte[] ByteUtil.convert32Bto16B(byte[] original) {
if (original.length != 32) {
return null; // error state
}
byte[] resultSecret = new byte[16];
for (int i = 0; i < 16; i++) {
resultSecret[i] = (byte) (original[i] ^ original[i + 16]);
}
return resultSecret;
}
The ACTIVATION_ID
must be in principle long, universally unique, random and with a temporary validity. UUID level 4 is therefore the selected format of this ID.
DO {
ACTIVATION_ID = UUID_GEN()
COUNT = SELECT COUNT(*) FROM ACTIVATION WHERE ACTIVATION.ID = ACTIVATION_ID
} WHILE (COUNT > 0);
Example of activation ID:
c564e700-7e86-4a87-b6c8-a5a0cc89683f
Note: A single UUID for an activation in CREATED state must be valid only for a limited period of time (activation time window), that should be rather short (in minutes at most).
Since the UUID is too long and inconvenient for practical applications, ACTIVATION_ID
is exchanged between client and server automatically, using ACTIVATION_ID_SHORT
- a shorter and more convenient identifier of an activation. This is the identifier user can rewrite or scan via the QR code. ACTIVATION_ID_SHORT
is a Base32 string, 2x 5 characters:
DO {
ACTIVATION_ID_SHORT = BASE32_RANDOM_STRING(5) + "-" + BASE32_RANDOM_STRING(5)
COUNT = SELECT COUNT(*) FROM ACTIVATION WHERE (ACTIVATION.STATE = 'CREATED' OR ACTIVATION.STATE = 'OTP_USED') AND ACTIVATION.ID_SHORT = ACTIVATION_ID_SHORT
} WHILE (COUNT > 0);
Example of short activation ID:
XDA57-24TBC
In order to explicitly bind a client application with the cryptography, an application ID and application secret are introduced. Both values follow the same format - 16B encoded as Base64, application ID must be unique.
Both identifiers are embedded in the PowerAuth 2.0 Client application (for example, defined as a constants in the source code).
Application ID is sent with every PowerAuth Signature as pa_applicationId
.
Application secret is a part of the PowerAuth signature (sent in pa_signature
), it enters the algorithm in final HMAC_SHA256 as a part of the DATA.
The ACTIVATION_OTP
is a Base32 string, 2 x 5 characters:
ACTIVATION_OTP = BASE32_RANDOM_STRING(5) + "-" + BASE32_RANDOM_STRING(5)
Example of activation OTP:
TB24C-A57XD
This format matches the ACTIVATION_ID_SHORT
format.
Note: A single ACTIVATION_OTP
must be valid only for a limited period of time (activation time window), that should be rather short (in minutes at most). Also, the activation OTP can be used only once - the moment client application sends and receives the encrypted public keys, it must be marked as "already used".
Entering ACTIVATION_ID_SHORT
, ACTIVATION_OTP
and ACTIVATION_SIGNATURE
can be expedited for example by using QR code for the storage. PowerAuth 2.0 defines using following format of information:
${ACTIVATION_ID_SHORT}-${ACTIVATION_OTP}#${ACTIVATION_SIGNATURE}
Example concatenated string:
XDA57-24TBC-TB24C-A57XD#1234567890
The device and server keys are generated using ECDH algorithm with P256 curve:
public KeyPair generateKeyPair() {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH", "BC"); // we assume BouncyCastle provider
kpg.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair kp = kpg.generateKeyPair();
return kp;
}
Shared key KEY_MASTER_SECRET
is generated using following algorithm (ECDH):
public SecretKey generateSharedKey(PrivateKey privateKey, PublicKey publicKey) throws InvalidKeyException {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC"); // we assume BouncyCastle provider
keyAgreement.init((Key) privateKey, new ECGenParameterSpec("secp256r1"));
keyAgreement.doPhase(publicKey, true);
final byte[] sharedSecret = keyAgreement.generateSecret();
byte[] resultSecret = new byte[16];
for (int i = 0; i < 16; i++) {
resultSecret[i] = (byte) (sharedSecret[i] ^ sharedSecret[i + 16]);
}
return convertBytesToSharedSecretKey(resultSecret);
}
All communication should be carried over a properly secured channel, such as HTTPS with correct server configuration and certificate issued with a trusted certificate authority. Client may implement certificate pinning to achieve better transport level security.
Server sends it's encrypted public key C_KEY_SERVER_PUBLIC
to the client with a signature SERVER_DATA_SIGNATURE
. This signature is created using the server's "Master Private Key" KEY_SERVER_MASTER_PRIVATE
. Since the same key is used for all activations, the "latent private key fingerprints" may accumulate over the time, making it simpler to attack the private key. Therefore, it is important to select the proper trusted certification authority to issue the keys and renew the key after certain time period. Usually, this also requires timely update of the clients that bundle the "Master Public Key".
The master key pair is generated using the same algorithm as normal key pair, see above (with P256 curve).
In order to generate the signature for given bytes (obtained from string by conversion using UTF-8 encoding), following code is used:
public byte[] signatureForBytes(byte[] bytes, PrivateKey privateKey) {
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", "BC"); // we assume BouncyCastle provider
ecdsaSign.initSign(privateKey);
ecdsaSign.update(bytes);
byte[] signature = ecdsaSign.sign();
return signature;
}
To verify the signature, following code is used:
public boolean isSignatureCorrectForBytes(byte[] bytes, byte[] signature, PublicKey publicKey)
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", "BC"); // we assume BouncyCastle provider
ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update(bytes);
boolean result = ecdsaVerify.verify(signature);
return result;
}
If you need any assistance, do not hesitate to drop us a line at hello@lime-company.eu.
PowerAuth 2.0 Specification
- Overview
- Basic Definitions
- Activation
- Key Derivation
- Checking Status
- Signatures
- MAC Token Based Authentication
- End-To-End Encryption
- Standard REST API
- Implementation Details
- List of Used Keys
Deployment
Applications
- PowerAuth Server
- PowerAuth Admin
- PowerAuth Push Server
- PowerAuth CMD Tool
- PowerAuth Mobile SDK
- SDK for RESTful APIs
- PowerAuth Web Flow
Development
Releases