mirrored from https://www.bouncycastle.org/repositories/pc-dart
-
Notifications
You must be signed in to change notification settings - Fork 139
Open
Description
Description
I'm facing an issue with the PointyCastle plugin in a Flutter application where RSA PKCS#1 v1.5 signatures, generated with SHA-256, fail to verify despite being valid. The same signatures are successfully verified using OpenSSL and other online RSA signature verification tools. This inconsistency suggests a potential problem with how PointyCastle handles RSA signature verification.
Context
We have implemented a Flutter plugin that utilizes RSA PKCS#1 v1.5 for signing and verifying data. The signing and verification processes are expected to use the SHA-256 hash algorithm.
- Key Generation: RSA keys are generated and stored securely.
- Signature Creation: A payload is signed using the private RSA key and the resulting signature is base64 encoded.
- Verification: The signature is verified against the payload using the corresponding RSA public key.
Steps to Reproduce
- Generate RSA Key Pair: Use the RSA algorithm to generate a 2048-bit key pair.
- Sign Payload: Use the RSA private key to sign a payload (
"Biometric payload") with PKCS#1 v1.5 padding and SHA-256 hashing. - Verify Signature in PointyCastle:
- Decode the base64 encoded signature.
- Hash the payload using SHA-256.
- Use PointyCastle's
Signerinitialized withSHA-256/RSAto verify the signature.
- Compare Verification Results:
- Verify the same signature using OpenSSL or other tools like 8gwifi.org RSA Signature Verification.
Observed Behavior
- PointyCastle: The verification result consistently returns
false, indicating that the signature is invalid. - OpenSSL: The verification returns
Verified OK, confirming that the signature is valid. - Online Tools: Verification using online tools also confirms the signature as valid.
Example Code
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:biometric_signature/biometric_signature.dart';
import 'package:pointycastle/pointycastle.dart';
import 'package:pointycastle/digests/sha256.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _biometricSignature = BiometricSignature();
String? _publicKey;
@override
void initState() {
super.initState();
asyncInit();
}
Future<void> asyncInit() async {
try {
final String? biometricsType =
await _biometricSignature.biometricAuthAvailable();
debugPrint("biometricsType : $biometricsType");
final bool doExist =
await _biometricSignature.biometricKeyExists() ?? false;
debugPrint("doExist : $doExist");
_publicKey = await _biometricSignature.createKeys();
debugPrint("Public key generated: $_publicKey");
const String payload = "Biometric payload";
debugPrint("Payload: $payload");
final String? signature = await _biometricSignature.createSignature(
options: {"payload": payload, "promptMessage": "You are Welcome!"});
debugPrint("Signature generated: $signature");
if (signature != null && _publicKey != null) {
final bool isValid = await verifySignatureWithPublicKey(
payload: payload, signature: signature, publicKeyPem: _publicKey!);
debugPrint("Is the signature valid? : $isValid");
}
} on PlatformException catch (e) {
debugPrint("PlatformException: ${e.message}");
debugPrint("Code: ${e.code}");
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: const Center(
child: Text('Running'),
),
),
);
}
Future<bool> verifySignatureWithPublicKey({
required String payload,
required String signature,
required String publicKeyPem,
}) async {
try {
// Decode the PEM public key to extract the modulus and exponent
final publicKeyComponents = decodePublicKeyFromPem(publicKeyPem);
final modulus = publicKeyComponents.modulus;
final exponent = publicKeyComponents.exponent;
debugPrint("Modulus: $modulus");
debugPrint("Exponent: $exponent");
// Create the RSA public key object
final rsaPublicKey = RSAPublicKey(modulus, exponent);
debugPrint(encodeRSAPublicKeyToPem(rsaPublicKey));
// Convert payload and signature to bytes
final messageBytes = utf8.encode(payload);
final signatureBytes = base64.decode(signature);
debugPrint("Message bytes: $messageBytes");
debugPrint("Signature bytes: $signatureBytes");
// Hash the payload using SHA-256
final sha256Digest = SHA256Digest();
final hashedMessage =
sha256Digest.process(Uint8List.fromList(messageBytes));
debugPrint("Hashed message: $hashedMessage");
// Use PointyCastle to verify the signature using PKCS#1 v1.5 with SHA-256
final verifier = Signer('SHA-256/RSA');
verifier.init(false, PublicKeyParameter<RSAPublicKey>(rsaPublicKey));
final rsaSignature = RSASignature(signatureBytes);
// Verify the signature
final verificationResult = verifier.verifySignature(
Uint8List.fromList(hashedMessage), rsaSignature);
debugPrint("Verification result from PointyCastle: $verificationResult");
return verificationResult;
} catch (e) {
debugPrint("Verification failed: $e");
return false;
}
}
RsaKeyComponents decodePublicKeyFromPem(String pem) {
final lines = pem
.split('\n')
.where((line) => line.isNotEmpty && !line.startsWith('---'))
.toList();
final base64String = lines.join('');
final asn1Parser = ASN1Parser(base64.decode(base64String));
final asn1Sequence = asn1Parser.nextObject() as ASN1Sequence;
// Navigate through the ASN.1 structure
final algorithmIdentifier = asn1Sequence.elements?[0] as ASN1Sequence;
final subjectPublicKeyBitString =
asn1Sequence.elements?[1] as ASN1BitString;
// Parse the public key
final publicKeyBytes = subjectPublicKeyBitString.stringValues!;
final publicKeyParser = ASN1Parser(Uint8List.fromList(publicKeyBytes));
final publicKeySequence = publicKeyParser.nextObject() as ASN1Sequence;
// Extract modulus and exponent
final modulus = publicKeySequence.elements?[0] as ASN1Integer;
final exponent = publicKeySequence.elements?[1] as ASN1Integer;
return RsaKeyComponents(
modulus: _decodeBigInt(modulus.valueBytes!),
exponent: _decodeBigInt(exponent.valueBytes!),
);
}
String encodeRSAPublicKeyToPem(RSAPublicKey publicKey) {
var algorithmSeq = ASN1Sequence();
var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0]));
algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption'));
algorithmSeq.add(paramsAsn1Obj);
var publicKeySeq = ASN1Sequence();
publicKeySeq.add(ASN1Integer(publicKey.modulus));
publicKeySeq.add(ASN1Integer(publicKey.exponent));
var publicKeySeqBitString =
ASN1BitString(stringValues: Uint8List.fromList(publicKeySeq.encode()));
var topLevelSeq = ASN1Sequence();
topLevelSeq.add(algorithmSeq);
topLevelSeq.add(publicKeySeqBitString);
var dataBase64 = base64.encode(topLevelSeq.encode());
var chunks = chunk(dataBase64, 64);
return '-----BEGIN PUBLIC KEY-----\n${chunks.join('\n')}\n-----END PUBLIC KEY-----';
}
List<String> chunk(String s, int chunkSize) {
var chunked = <String>[];
for (var i = 0; i < s.length; i += chunkSize) {
var end = (i + chunkSize < s.length) ? i + chunkSize : s.length;
chunked.add(s.substring(i, end));
}
return chunked;
}
BigInt _decodeBigInt(List<int> bytes) {
var negative = bytes.isNotEmpty && bytes[0] & 0x80 == 0x80;
var unsignedBytes = negative ? [0] + bytes : bytes;
var result = BigInt.parse(
unsignedBytes
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.join(),
radix: 16);
return negative ? result.toUnsigned(8 * bytes.length) : result;
}
}
class RsaKeyComponents {
final BigInt modulus;
final BigInt exponent;
RsaKeyComponents({required this.modulus, required this.exponent});
}Output
Restarted application in 779ms.
I/flutter (24086): biometricsType : fingerprint
I/flutter (24086): doExist : true
D/EGL_emulation(24086): app_time_stats: avg=1580024.12ms min=1580024.12ms max=1580024.12ms count=1
I/flutter (24086): Public key generated: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsLqX79Ts4BIphMGnn6Bq/Ozs1pP/FshiiMz5HGWfQYrkV4E0U8FSzDeOuxrz8jpAxL7OBsBjANKNHgDVj7CPrQlgBc7NM4td0n0JKPkUmyUM9v+cyolT+tU1RW5fojLq37DWihWjoGVZrnH+bniPcjDyAtaRrQCxHkdXO748B3FGCISoI+CNwCy4/ONjef760TJIE+SrUylJcDGqxaULmnO/eVx8kv+E1SQy0jZh8RMPwJW+wbikwiE5U47VQW444gV22TfjmhjkQKGtvyOM6LQY83XFaM8sRCrMDCvFxAAazSIGqpWHSruEeNXri7cgPzGdAaVGpK8MvmhUURGOkQIDAQAB
I/flutter (24086): Payload: Biometric payload
I/flutter (24086): Signature generated: hdOQNRoNpYjUHERpkhv5R/hQH8Ip/QVex1JET5fRrRsrnYeU0b/k9QaC49cwCmnKIrwmUEdiDyZ2l81spqEetiIHQhRWiMXpCBT51HwtIZT8O6lpQ8XbgWI2EXRxjGX1tnxUA6rHOV7/JSkR2fxhL2NFnLv46gK47L7SKxgcUnE/1VCLXOWWsFfLv5w/nPYVYrgx7zlKpFd58f30C3HruP4kf70ntyt5omBfIeEUZIzwjbAWbbMpVnVzKhPz7Z0sMAvZvNa9Bu2wKHU3UD3ZpRdzrVdpJltRO9zIqcliweTkeZr2EqMTausd/NGtTcbhciiBZRI5JQqgn+uh7oaT3Q==
I/flutter (24086): Modulus: 22309954359859329121179930388900541711115245118790342773149323158468033793502777805928933769458950939680792288496008015161813611290667687314911596271566701779546864209817828289468730866000266211271945242607572166756096617656961677631092347128721676224433915494332399668572155673649009706409562172893228409810175870741055420557785753492853985700796586502400051925456129658284927459816217421431252445994608511589723687089603562911661749376313961800748478282271040359205195968502232614302888027085137391081505579397725859302240582618309173566087675629333761984767678108698536365905213799756652646852553974671053144821393
I/flutter (24086): Exponent: 65537
I/flutter (24086): -----BEGIN PUBLIC KEY-----
I/flutter (24086): MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsLqX79Ts4BIphMGnn6Bq
I/flutter (24086): /Ozs1pP/FshiiMz5HGWfQYrkV4E0U8FSzDeOuxrz8jpAxL7OBsBjANKNHgDVj7CP
I/flutter (24086): rQlgBc7NM4td0n0JKPkUmyUM9v+cyolT+tU1RW5fojLq37DWihWjoGVZrnH+bniP
I/flutter (24086): cjDyAtaRrQCxHkdXO748B3FGCISoI+CNwCy4/ONjef760TJIE+SrUylJcDGqxaUL
I/flutter (24086): mnO/eVx8kv+E1SQy0jZh8RMPwJW+wbikwiE5U47VQW444gV22TfjmhjkQKGtvyOM
I/flutter (24086): 6LQY83XFaM8sRCrMDCvFxAAazSIGqpWHSruEeNXri7cgPzGdAaVGpK8MvmhUURGO
I/flutter (24086): kQIDAQAB
I/flutter (24086): -----END PUBLIC KEY-----
I/flutter (24086): Message bytes: [66, 105, 111, 109, 101, 116, 114, 105, 99, 32, 112, 97, 121, 108, 111, 97, 100]
I/flutter (24086): Signature bytes: [133, 211, 144, 53, 26, 13, 165, 136, 212, 28, 68, 105, 146, 27, 249, 71, 248, 80, 31, 194, 41, 253, 5, 94, 199, 82, 68, 79, 151, 209, 173, 27, 43, 157, 135, 148, 209, 191, 228, 245, 6, 130, 227, 215, 48, 10, 105, 202, 34, 188, 38, 80, 71, 98, 15, 38, 118, 151, 205, 108, 166, 161, 30, 182, 34, 7, 66, 20, 86, 136, 197, 233, 8, 20, 249, 212, 124, 45, 33, 148, 252, 59, 169, 105, 67, 197, 219, 129, 98, 54, 17, 116, 113, 140, 101, 245, 182, 124, 84, 3, 170, 199, 57, 94, 255, 37, 41, 17, 217, 252, 97, 47, 99, 69, 156, 187, 248, 234, 2, 184, 236, 190, 210, 43, 24, 28, 82, 113, 63, 213, 80, 139, 92, 229, 150, 176, 87, 203, 191, 156, 63, 156, 246, 21, 98, 184, 49, 239, 57, 74, 164, 87, 121, 241, 253, 244, 11, 113, 235, 184, 254, 36, 127, 189, 39, 183, 43, 121, 162, 96, 95, 33, 225, 20, 100, 140, 240, 141, 176, 22, 109, 179, 41, 86, 117, 115, 42, 19, 243, 237, 157, 44, 48, 11, 217, 188, 214, 189, 6, 237, 176, 40, 117, 55, 80, 61, 217, 165, 23, 115, 173, 87, 105, 38, 91, 81, 59, 220, 200, 169, 201, 98,
I/flutter (24086): Hashed message: [253, 59, 98, 240, 35, 254, 230, 185, 153, 77, 190, 237, 86, 49, 68, 128, 206, 54, 84, 41, 253, 107, 224, 201, 147, 220, 180, 198, 91, 70, 108, 75]
I/flutter (24086): Verification result from PointyCastle: false
I/flutter (24086): Is the signature valid? : false
Metadata
Metadata
Assignees
Labels
No labels