0

I have written two programs which will exchange X25519 public keys and then calculate shared secret. This is just a test, so public keys are exchanged trough text files (in PEM format), program is until you tell it to continue in order to generate keypairs and exchange them.

First one is in Dart:

import 'dart:convert';
import 'dart:io';
import 'package:cryptography/cryptography.dart';
import 'package:pem/pem.dart';

Future<void> main(List<String> arguments) async {
  //https://pub.dev/documentation/cryptography/latest/cryptography/X25519-class.html

  final algorithm = Cryptography.instance.x25519();
  final keyPair = await algorithm.newKeyPair();
  var file = File('/home/razj/dartPublic.txt');
  file.writeAsStringSync(PemCodec(PemLabel.publicKey)
      .encode(await keyPair.extractPublicKeyBytes()));

  print('PRESS AFTER C# PROGRAM GENERATES KEYPAIR');
  stdin.readLineSync();

  String remotePem = File('/home/razj/sharpPublic.txt').readAsStringSync();
  SimplePublicKey remotePublicKey = SimplePublicKey(
      PemCodec(PemLabel.publicKey).decode(remotePem),
      type: KeyPairType.x25519);
  final sharedSecretKey = await algorithm.sharedSecretKey(
    keyPair: keyPair,
    remotePublicKey: remotePublicKey,
  );
  List<int> sharedKeyBytes = await sharedSecretKey.extractBytes();
  print(base64.encode(sharedKeyBytes));
}

extension SimpleKeyPairExtension on SimpleKeyPair {
  Future<List<int>> extractPublicKeyBytes() {
    return extract().then((value) => value.bytes);
  }
}

Second is in .NET CORE:

using System.Security.Cryptography;
using X25519;

internal class Program
{
    private static void Main(string[] args)
    {
        //https://github.com/HirbodBehnam/X25519-CSharp

        var keyPair = X25519KeyAgreement.GenerateKeyPair();
        File.WriteAllText("/home/razj/sharpPublic.txt", new string(PemEncoding.Write("PUBLIC KEY", keyPair.PublicKey)));

        Console.WriteLine("PRESS AFTER DART PROGRAM GENERATES KEYPAIR");
        Console.ReadKey(true);

        string remotePem = File.ReadAllText("/home/razj/dartPublic.txt").Replace("-----BEGIN PUBLIC KEY-----\n", "").Replace("\n-----END PUBLIC KEY-----\n", "");
        byte[] sharedKeyBytes = X25519KeyAgreement.Agreement(keyPair.PrivateKey, Convert.FromBase64String(remotePem));
    
        Console.WriteLine(Convert.ToBase64String(sharedKeyBytes));
    }
}

In the end of each program I print base64 encoded byte array which represents the shared key. Unfortunately outputs doesn't match. Any idea how to fix this or what might be wrong? Thanks for answering.

jsfrz
  • 99
  • 9
  • Could you check the binary form of the remote public key in both instances to see if they are structurally different (after decoding)? `Convert.FromBase64String(remotePem)` looks a bit suspicious to me. – Maarten Bodewes Sep 15 '22 at 23:25
  • 1
    On my machine `await keyPair.extractPublicKeyBytes()` does not return the public key, but the private key (probably a bug). I get the public one with `(await keyPair.extractPublicKey()).bytes`. Apart from that, on my machine the shared secrets are identical (if the correct keys are used). – Topaco Sep 16 '22 at 08:27
  • 1
    Note that your PEM keys are invalid, but this has no impact. The PEM packages you use convert between DER and PEM encoding. However, you simply apply the raw 32 bytes key instead of the DER encoded key, so the PEM key is invalid. This has no consequence, since the reverse direction returns the raw keys that both libraries use in the end. It is unnecessary though, just apply the (Base64 encoded) raw keys directly. – Topaco Sep 16 '22 at 08:37
  • Yes, the problem was unnecessary and broken conversion to PEM. Exchanging raw bytes encoded to Base64 instead solved this. – jsfrz Sep 16 '22 at 15:46

1 Answers1

1

As @Topaco said,

However, you simply apply the raw 32 bytes key instead of the DER encoded key, so the PEM key is invalid. This has no consequence, since the reverse direction returns the raw keys that both libraries use in the end. It is unnecessary though, just apply the (Base64 encoded) raw keys directly.

Just exchanged keys by encoding raw key bytes to Base64 string:

Dart:

import 'dart:io';
import 'dart:convert';
import 'package:cryptography/cryptography.dart';

Future<void> main(List<String> arguments) async {
  //https://pub.dev/documentation/cryptography/latest/cryptography/X25519-class.html

  final algorithm = Cryptography.instance.x25519();
  final keyPair = await algorithm.newKeyPair();
  var file = File('/home/razj/dartPublic.txt');
  SimplePublicKey publicKey = await keyPair.extractPublicKey();
  //saving key bytes as base64 string
  file.writeAsStringSync(base64.encode(publicKey.bytes));

  print('PRESS AFTER C# PROGRAM GENERATES KEYPAIR');
  stdin.readLineSync();

  String remoteKeyBase64 =
      File('/home/razj/sharpPublic.txt').readAsStringSync();
  SimplePublicKey remotePublicKey =
      SimplePublicKey(base64.decode(remoteKeyBase64), type: KeyPairType.x25519);
  final sharedSecretKey = await algorithm.sharedSecretKey(
    keyPair: keyPair,
    remotePublicKey: remotePublicKey,
  );
  List<int> sharedKeyBytes = await sharedSecretKey.extractBytes();
  print(base64.encode(sharedKeyBytes));
}

.NET CORE

using X25519;

internal class Program
{
    private static void Main(string[] args)
    {
        //https://github.com/HirbodBehnam/X25519-CSharp

        var keyPair = X25519KeyAgreement.GenerateKeyPair();
        //saving key bytes as base64 string
        File.WriteAllText("/home/razj/sharpPublic.txt", Convert.ToBase64String(keyPair.PublicKey));

        Console.WriteLine("PRESS AFTER DART PROGRAM GENERATES KEYPAIR");
        Console.ReadKey(true);

        string remoteKeyBase64 = File.ReadAllText("/home/razj/dartPublic.txt");
        byte[] sharedKeyBytes = X25519KeyAgreement.Agreement(keyPair.PrivateKey, Convert.FromBase64String(remoteKeyBase64));
    
        Console.WriteLine(Convert.ToBase64String(sharedKeyBytes));
    }
}

Dart output:

Hz2Rf2nUFbwmg4wgaXOl3qAi8ha5h61fHcMOXpNQ23o=

.NET-CORE output

Hz2Rf2nUFbwmg4wgaXOl3qAi8ha5h61fHcMOXpNQ23o=

jsfrz
  • 99
  • 9