iRyanBell

Cryptographic Darts

June 01, 2019

I have an idea for a password-less authentication service. My idea is to eliminate the process of user-generated (often less-than-secure) passwords, and instead use a mobile app asymmetric keypair store which uses signatures for authentication. This would give us anonymity, identity, and security.

The flow could work like this:

  1. A user sees a QR-like image on a site.
  2. They point their phone at the image.
  3. The app on the phone reads the data from the image, cryptographically signs the message, and posts the signature to the reference site’s back-end.
  4. The site’s front-end listens for this action, then redirects soon after the user pointed their phone at the screen and stores the auth signature token.

Alternatively, a third-party could store a key then send the signature to a user’s email, similar to a one-time-pass, albeit at the cost of some anonymity.

To see where this might lead, I wanted to check out the available cryptographic libraries for Dart. There’s a great port of the Bouncy Castle Java library at https://github.com/PointyCastle/pointycastle.

https://www.bouncycastle.org/

Here at the Bouncy Castle, we believe in encryption. That’s something that’s near and dear to our hearts. We believe so strongly in encryption, that we’ve gone to the effort to provide some for everybody, and we’ve now been doing it for almost 20 years!

https://www.bouncycastle.org/about.html

Originally, in the late 1990s, the Legion of the Bouncy Castle was simply a number of individuals united both in their interests of Cryptography and Open Source. The first official release of the Bouncy Castle APIs appeared in May 2000 and was about 27,000 lines long. The project grew steadily with a C# version of the Java APIs being added in 2006. By 2012 with the Java code base well past 300,000 lines and the C# one over 140,000 it started becoming obvious that a bit more organisation was required to maintain both progress and the quality of the APIs.

To begin putting this fantastic library to use, we need a function to generate a random seed for our keypair generator.

FortunaRandom genSecure() {
  final secureRandom = FortunaRandom();
  final random = Random.secure();

  const int seedLength = 32;
  const int randomMax = 255;
  final Uint8List uint8list = Uint8List(seedLength);

  for (int i=0; i < seedLength; i++) {
    uint8list[i] = random.nextInt(randomMax);
  }

  final KeyParameter keyParameter = KeyParameter(uint8list);
  secureRandom.seed(keyParameter);

  return secureRandom;
}

We also need some conversion functions for moving between unsigned integer lists, strings, and big integers:

Uint8List stringToUint8List(String s) => Uint8List.fromList(s.codeUnits);

BigInt uint8ListToBigInt(Uint8List bytes) {
  BigInt read(int start, int end) {
    if (end - start <= 4) {
      int result = 0;
      for (int i = end - 1; i >= start; i--) {
        result = result * 256 + bytes[i];
      }

      return BigInt.from(result);
    }
    int mid = start + ((end - start) >> 1);
    final result = read(start, mid) + read(mid, end) * (BigInt.one << ((mid - start) * 8));

    return result;
  }

  return read(0, bytes.length);
}

Uint8List bigIntToUint8List(BigInt number) {
  int bytes = (number.bitLength + 7) >> 3;
  final b256 = BigInt.from(256);
  final result = Uint8List(bytes);

  for (int i = 0; i < bytes; i++) {
    result[i] = number.remainder(b256).toInt();
    number = number >> 8;
  }

  return result;
}

With these in place, we can generate our keypairs. The secp256k1 elliptic curve algorithm has some nice features. https://en.bitcoin.it/wiki/Secp256k1

Map genKeypair58() {
  final ecParams = ECKeyGeneratorParameters(ECDomainParameters('secp256k1'));
  final params = ParametersWithRandom<ECKeyGeneratorParameters>(ecParams, genSecure());
  final keyGenerator = KeyGenerator('EC');
  keyGenerator.init(params);

  final keyPair = keyGenerator.generateKeyPair();
  ECPrivateKey priKey = keyPair.privateKey;
  ECPublicKey pubKey = keyPair.publicKey;

  Uint8List priKeyUint8 = bigIntToUint8List(priKey.d);
  Uint8List pubKeyUint8 = pubKey.Q.getEncoded();

  final String priKey58 = bs58check.encode(priKeyUint8);
  final String pubKey58 = bs58check.encode(pubKeyUint8);

  return {
    'publicKey': pubKey58,
    'privateKey': priKey58,
  };
}

Now, to sign messages with our private key, we can use:

String signString(String privateKey58, String msgToSign) {
  final Signer signer = Signer('SHA-1/ECDSA');
  final privateKey = ECPrivateKey(
    uint8ListToBigInt(bs58check.decode(privateKey58)),
    ECDomainParameters('secp256k1'),
  );

  final privParams = () => ParametersWithRandom(
    PrivateKeyParameter<ECPrivateKey>(privateKey),
    genSecure(),
  );

  signer.reset();
  signer.init(true, privParams());
  final Signature s = signer.generateSignature(stringToUint8List(msgToSign));
  final String sig = s.toString();
  final List sigXY = sig.substring(1, sig.length - 1).split(',');
  final Uint8List sigX = bigIntToUint8List(BigInt.parse(sigXY[0]));
  final Uint8List sigY = bigIntToUint8List(BigInt.parse(sigXY[1]));
  final String sig58 = bs58check.encode(Uint8List.fromList(sigX + sigY));

  return sig58;
}

Finally, to confirm a signature with an individual’s public key, we can use:

bool checkSig(String msgToSign, String sig58, String publicKey58) {
  final Signer signer = Signer('SHA-1/ECDSA');

  final chkSX = uint8ListToBigInt(bs58check.decode(sig58).sublist(0, 32));
  final chkSY = uint8ListToBigInt(bs58check.decode(sig58).sublist(32, 64));
  final Signature chkSig = ECSignature(chkSX, chkSY);
  final verifyParams = () => PublicKeyParameter(
    ECPublicKey(
      ECCurve_secp256k1().curve.decodePoint(bs58check.decode(publicKey58)),
      ECDomainParameters('secp256k1'),
    ),
  );
  signer.reset();
  signer.init(false, verifyParams());
  final bool ok = signer.verifySignature(stringToUint8List(msgToSign), chkSig);

  return ok;
}

To test these functions we’ll run:

void main() {
  final Map keypair = genKeypair58();

  print('Private key (base58): ${keypair['privateKey']}');
  print('Public key (base58): ${keypair['publicKey']}');

  final String msgToSign = 'hello';
  final String sig58 = signString(keypair['privateKey'], msgToSign);
  final bool chkSig = checkSig(msgToSign, sig58, keypair['publicKey']);
  
  print('Signature: $sig58');
  print('Validation: $chkSig');
}

In the console, we should see:

Private key (base58): 78bFvVrzviVe4uGKJ5e2e7L6Vye1TbXTAxsFsqwkHAe3G4okx
Public key (base58): 72s1aW6HN5DXuvwcBc6t8CxkyKWPtCV6RMg24eZkFdsLddnYVB
Signature: UTwfKCLy9vxKvnh59j1E9DaKuH817Kv31wiBVAbxWNTopaLP6kSC37cr1dbFAPkmD62fjyCA3w8XAPQmXapDc3p4yoVdp
Validation: true

On the javascript front, I’d like to explore some alternatives to QR codes. While the QR is well-vetted, it’s not particularly aesthetic. To begin this exploration process, I need a way to break a URL down to a fixed array of bits — the actual zero’s and ones — I can then rearrange these to procedurally build a new symbolic image with the data’s digital representation.

While this will give me URL to image encoding, it will not decode the image. That actually seems like a very difficult process. What I’d like to try, is to train a convolutional neural network to run the symbolic representation through machine vision. (How hard can it be?)

Here’s what I came up with for terminating a UTF8-encoded string with null characters, then padding it to a 140 character limit in 2**7 ascii representation.

const binary2Text = str =>
  str.match(/.{1,7}/g).map(bits =>
    String.fromCharCode(parseInt(bits, 2))
  ).join('').split('\u0000')[0]

const textToBinary = str => {
	const encoded = str.split('').map(char =>
		char.charCodeAt(0).toString(2).padStart(7, '0')
	)

	while (encoded.length < 140) encoded.push('0000000')

	return encoded.join('')
}

const domain = 'https://iryanbell.com'
const bin = textToBinary(domain)

console.log(bin)
console.log(binary2Text(bin))

We should see:

11010001110100111010011100001110011011101001011110101111110100111100101111001110000111011101100010110010111011001101100010111011000111101111110110100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

https://iryanbell.com

From here, we have a starting point. It’s just a quick sketch, but I wanted to jot this down somewhere.