Data Signing
The main feature of the PowerAuth protocol is data signing. PowerAuth supports the following types of signatures:
- Symmetric Multi-Factor Authentication Code: Suitable for most operations, such as login, payment approval, or confirming changes in settings.
- Symmetric Offline Multi-Factor Authentication Code: Suitable for operations where the authentication code is validated over an out-of-band channel.
- Asymmetric Private Key Signature: Suitable for documents where a strong one-sided signature is required.
- Verify Server-Signed Data: Suitable for receiving arbitrary data from the server.
Authentication Codes
Symmetric Multi-Factor Authentication Code
Create a PowerAuthAuthentication object that contains the required authentication factors:
// 2FA authentication code with the possession factor and a PIN.
final password = await PowerAuthPassword.fromString(
"1234",
destroyOnUse: false,
);
final authentication = PowerAuthAuthentication.password(
password,
);
This password is reusable so request-body and query-parameter operations can share the same authentication object. Always release a reusable password in a finally block.
For a request with a body, pass the raw body bytes to authenticationHeaderForRequestWithBody(). For a request with query parameters, use authenticationHeaderForRequestWithParams():
final body = Uint8List.fromList(
utf8.encode(jsonEncode({"payment": "yes"})),
);
const params = {
"param1": "value1",
"param2": "value2",
};
try {
final bodyHeader = await powerAuth.authenticationHeaderForRequestWithBody(
authentication,
"POST",
"/payment/create",
body,
);
final bodyHeaderName = bodyHeader.name;
final bodyHeaderValue = bodyHeader.value;
final paramsHeader = await powerAuth.authenticationHeaderForRequestWithParams(
authentication,
"GET",
"/payment/create",
params,
);
final paramsHeaderName = paramsHeader.name;
final paramsHeaderValue = paramsHeader.value;
} catch (e) {
// Handle an error.
} finally {
await password.release();
}
Each result is an HTTP header. Add its name and value to the corresponding request.
To use biometry, create a biometric authentication object:
final authentication = PowerAuthAuthentication.biometry(
biometricPrompt: PowerAuthBiometricPrompt(
promptMessage: "Authenticate to process the payment",
promptTitle: "Authenticate", // Android only
fallbackButtonTitle: "Enter PIN", // iOS only
),
);
try {
final header = await powerAuth.authenticationHeaderForRequestWithBody(
authentication,
"POST",
"/payment/create",
body,
);
} on PowerAuthException catch (e) {
if (e.code == PowerAuthErrorCode.biometryCancel) {
// The user canceled the biometric dialog.
} else if (e.code == PowerAuthErrorCode.biometryFallback) {
// The user selected the fallback button on iOS.
} else {
// Handle a different error.
}
}
Request Synchronization
It is recommended that your application executes only one authenticated request at a time. Authentication codes use a counter as logical time, so the server must validate requests in the same order in which the SDK creates them.
Symmetric Offline Multi-Factor Authentication Code
An offline authentication code is a short string that a user can transfer through a separate channel. Pass the nonce as a Base64-encoded string and the body as raw bytes:
final authentication = PowerAuthAuthentication.password(
await PowerAuthPassword.fromString("1234"),
);
final body = Uint8List.fromList(utf8.encode(jsonEncode(operation)));
try {
final authenticationCode = await powerAuth.offlineSignature(
authentication,
"/confirm/offline/operation",
nonce,
body,
);
print("Offline authentication code: $authenticationCode");
} catch (e) {
// Handle an error.
}
Show the calculated code to the user. The user can enter it in the other application that validates the operation.
Digital Signatures
Digital signatures are another form of data authentication supported by the PowerAuth protocol. The SDK provides one interface for computing and verifying digital signatures and MAC tokens.
Signature Key Identifiers
The following key categories are available:
- Master public keys verify data signed by the server and do not require an activation.
- Server public keys are personalized for an activation and verify data signed by the server.
- Device private and public keys belong to an activation and sign or verify data on the device.
- MAC keys are personalized symmetric keys that verify MACs calculated by the server.
| Key identifier | Key type | Signature | Activation | Sign | Verify |
|---|---|---|---|---|---|
master |
Any | Any or hybrid | No | No | Yes |
masterEc |
EC | ECDSA | No | No | Yes |
masterMlDsa |
ML-DSA | ML-DSA | No | No | Yes |
server |
Any | Any or hybrid | Yes | No | Yes |
serverEc |
EC | ECDSA | Yes | No | Yes |
serverMlDsa |
ML-DSA | ML-DSA | Yes | No | Yes |
device |
Any | Any or hybrid | Yes | Yes | Yes |
deviceEc |
EC | ECDSA | Yes | Yes | Yes |
deviceMlDsa |
ML-DSA | ML-DSA | Yes | Yes | Yes |
macPersonalized |
MAC | KMAC | Yes | No | Yes |
EC keys are always available. ML-DSA keys are available only with PowerAuthAlgorithm.p384l3 and PowerAuthAlgorithm.p384l5. MAC keys are available with every algorithm except PowerAuthAlgorithm.legacy.
Selecting a key without its exact type, such as master, can select both EC and ML-DSA keys. Because a hybrid raw-signature format is not standardized, generic key identifiers are supported only by the JWS functions. Use an exact key type for raw digital signatures.
Sign Data With Device Private Key
An asymmetric private key signature uses a device private key from the Secure Vault. The user must authenticate with at least two factors before the SDK can use the key. Select an exact device key type:
final authentication = PowerAuthAuthentication.password(
await PowerAuthPassword.fromString("1234"),
);
final data = Uint8List.fromList(utf8.encode("hello"));
try {
final signature = await powerAuth.calculateDigitalSignature(
authentication,
data,
PowerAuthSignatureKeyId.deviceMlDsa,
);
// Use data and signature.
} catch (e) {
// Handle an authentication or network error.
}
Biometric authentication can also access the device private key when the SDK is not configured with PowerAuthAlgorithm.legacy.
Create JSON Web Signature With Device Private Key
The device private key can also create a JSON Web Signature (JWS). The following example creates both a non-compact JWS from generic data and a compact signed JWT. It uses a reusable password so both operations can share the same authentication object:
final password = await PowerAuthPassword.fromString(
"1234",
destroyOnUse: false,
);
final authentication = PowerAuthAuthentication.password(
password,
);
try {
final data = Uint8List.fromList(utf8.encode("hello"));
final jws = await powerAuth.calculateJwsSignature(
authentication,
data,
null, // Do not add "typ" to the JWS protected header.
false, // Return a full JWS object.
PowerAuthSignatureKeyId.deviceMlDsa,
);
final claimsData = Uint8List.fromList(utf8.encode(jsonEncode({
"sub": "user-id",
"first_name": "John",
"last_name": "Appleseed",
})));
final jwt = await powerAuth.calculateJwsSignature(
authentication,
claimsData,
"JWT",
true, // Return the compact JWT form.
PowerAuthSignatureKeyId.deviceMlDsa,
);
} finally {
await password.release();
}
Verify Server-Signed Data
Use verifyDigitalSignature() to verify raw data. Select the exact server key that signed the data:
try {
await powerAuth.verifyDigitalSignature(
signature,
data,
PowerAuthSignatureKeyId.serverMlDsa,
);
// The signature is valid.
} on PowerAuthException catch (e) {
if (e.code == PowerAuthErrorCode.wrongSignature) {
// The signature is not valid.
} else {
// Handle another failure, such as a missing activation.
}
}
The method completes without a result when the signature is valid and reports PowerAuthErrorCode.wrongSignature when verification fails.
Verify Data Encoded in a QR Code
To verify data authenticated by the server with an activation-personalized MAC, use macPersonalized:
try {
await powerAuth.verifyDigitalSignature(
signature,
data,
PowerAuthSignatureKeyId.macPersonalized,
);
// The MAC is valid.
} on PowerAuthException catch (e) {
if (e.code == PowerAuthErrorCode.wrongSignature) {
// The MAC is not valid.
}
}
Verify JSON Web Signature
To verify a non-compact JWS created by the server, use:
await powerAuth.verifyJwsSignature(
serverJws,
false, // Expect a full JWS object.
true, // Require all selected signatures to be valid.
PowerAuthSignatureKeyId.server,
);
The verifyJwsSignature() parameters have the following meaning:
signaturecontains the JWS- or JWT-signed data.compactindicates whethersignaturecontains a compact JWT (true) or a full JWS object (false).strictrequires all selected keys to verify their corresponding signatures whentrue. This is the recommended setting. Whenfalse, verification succeeds if at least one selected key matches a valid signature, but invalid or mismatched signatures still cause an error.signatureKeyIdselects the keys used for verification. JWS verification does not supportPowerAuthSignatureKeyId.macPersonalized.
A compact JWT contains only one signature. Use an exact key type, such as deviceEc, deviceMlDsa, serverEc, or serverMlDsa, for compact signatures. Generic identifiers such as device or server can select both EC and ML-DSA keys with PowerAuthAlgorithm.p384l3 and PowerAuthAlgorithm.p384l5; use those identifiers with non-compact JWS.
Setting strict to false is generally not recommended. An attacker could remove or replace a stronger post-quantum signature with a weaker signature without detection.
Creating Certificate Signing Request
Use createCertificateSigningRequest() to create an X.509 certificate signing request in PEM format. Prefix every subject alternative name with its type:
final authentication = PowerAuthAuthentication.password(
await PowerAuthPassword.fromString("1234"),
);
final csr = await powerAuth.createCertificateSigningRequest(
authentication,
{
"CN": "wultra.com",
"O": "Wultra",
"C": "CZ",
},
[
"IP: 192.168.1.10",
"email: [email protected]",
],
PowerAuthSignatureKeyId.deviceMlDsa,
);
Biometric authentication can also create a CSR when the SDK is not configured with PowerAuthAlgorithm.legacy.
Getting Device Public Keys
Use exportDevicePublicKeys() to export public keys for the current activation:
final keys = await powerAuth.exportDevicePublicKeys(
PowerAuthDevicePublicKeyFormat.der,
);
for (final key in keys) {
print("${key.keyAlgorithm}: ${base64Encode(key.keyData)}");
}
Available formats:
PowerAuthDevicePublicKeyFormat.derexports a binary X.509 SubjectPublicKeyInfo structure.PowerAuthDevicePublicKeyFormat.rawexports EC keys in ANSI X9.63 format and ML-DSA keys as raw public-key bytes.