Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSA PKCS#1 v1.5 SHA-256 Signature Verification Failure in PointyCastle #239

Open
chamodanethra opened this issue Jun 11, 2024 · 1 comment

Comments

@chamodanethra
Copy link

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

  1. Generate RSA Key Pair: Use the RSA algorithm to generate a 2048-bit key pair.
  2. Sign Payload: Use the RSA private key to sign a payload ("Biometric payload") with PKCS#1 v1.5 padding and SHA-256 hashing.
  3. Verify Signature in PointyCastle:
    • Decode the base64 encoded signature.
    • Hash the payload using SHA-256.
    • Use PointyCastle's Signer initialized with SHA-256/RSA to verify the signature.
  4. Compare Verification Results:

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
@Ashenafi-pixel
Copy link

Hey mate same here did you find any solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants