pc-dart icon indicating copy to clipboard operation
pc-dart copied to clipboard

Grain128 engine

Open nicolasgarnet opened this issue 3 years ago • 3 comments

Hi,

I see that the grain128a stream cipher is available in the original java bouncycastle.

Is there any plan to implement it here as well ?

Thank you,

nicolasgarnet avatar Jun 14 '21 14:06 nicolasgarnet

It doesn't look too complicated; I can try giving it a shot. Reference implementation: https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/engines/Grain128Engine.java

AKushWarrior avatar Jun 17 '21 16:06 AKushWarrior

It would be great, thank you.

nicolasgarnet avatar Jun 22 '21 15:06 nicolasgarnet

Here is a draft. I contacted @Ephenodrom to help me to integrate correctly this class and to write correctly processmethod After that, we will add tests.

import 'dart:typed_data';

import '../api.dart';

class Grain128Engine implements StreamCipher {
  static const STATE_SIZE = 4;

  late Uint8List workingKey;
  late Uint8List workingIV;
  late Uint8List out;
  late List<int> lfsr;
  late List<int> nfsr;

  int output = 0;
  int index = 4;
  bool initialised = false;

  @override
  String get algorithmName => 'Grain-128';

  @override
  void init(bool forEncryption, CipherParameters? params) {
    if (!(params is ParametersWithIV)) {
      throw ArgumentError('Grain-128 Init parameters must include an IV');
    }

    final ivParams = params;
    final iv = ivParams.iv;
    if (iv.length != 12) {
      throw ArgumentError('Grain-128 requires exactly 12 bytes of IV');
    }

    if (!(ivParams.parameters is KeyParameter)) {
      throw ArgumentError('Grain-128 init parameters must include a key');
    }

    final key = ivParams.parameters as KeyParameter;
    final keyBytes = key.key;
    if (keyBytes.length != 16) {
      throw ArgumentError('Grain-128 key must be 128 bits long');
    }

    /**
     * Initialize variables.
     */
    workingIV = Uint8List.fromList(keyBytes);
    workingKey = Uint8List.fromList(keyBytes);
    lfsr = List.generate(STATE_SIZE, (_) => 0);
    nfsr = List.generate(STATE_SIZE, (_) => 0);
    out = Uint8List(4);

    initialised = true;

    reset();
  }

  /// 256 clocks initialization phase.
  void initGrain() {
    for (var i = 0; i < 8; i++) {
      output = getOutput();
      nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output);
      lfsr = shift(lfsr, getOutputLFSR() ^ output);
    }
    initialised = true;
  }

  /// Get output from non-linear function g(x).
  ///
  /// @return Output from NFSR.
  int getOutputNFSR() {
    final b0 = nfsr[0];
    final b3 = nfsr[0] >> 3 | nfsr[1] << 29;
    final b11 = nfsr[0] >> 11 | nfsr[1] << 21;
    final b13 = nfsr[0] >> 13 | nfsr[1] << 19;
    final b17 = nfsr[0] >> 17 | nfsr[1] << 15;
    final b18 = nfsr[0] >> 18 | nfsr[1] << 14;
    final b26 = nfsr[0] >> 26 | nfsr[1] << 6;
    final b27 = nfsr[0] >>> 27 | nfsr[1] << 5;
    final b40 = nfsr[1] >>> 8 | nfsr[2] << 24;
    var b48 = nfsr[1] >>> 16 | nfsr[2] << 16;
    var b56 = nfsr[1] >>> 24 | nfsr[2] << 8;
    var b59 = nfsr[1] >>> 27 | nfsr[2] << 5;
    var b61 = nfsr[1] >>> 29 | nfsr[2] << 3;
    var b65 = nfsr[2] >>> 1 | nfsr[3] << 31;
    var b67 = nfsr[2] >>> 3 | nfsr[3] << 29;
    var b68 = nfsr[2] >>> 4 | nfsr[3] << 28;
    var b84 = nfsr[2] >>> 20 | nfsr[3] << 12;
    var b91 = nfsr[2] >>> 27 | nfsr[3] << 5;
    var b96 = nfsr[3];

    return b0 ^
        b26 ^
        b56 ^
        b91 ^
        b96 ^
        b3 & b67 ^
        b11 & b13 ^
        b17 & b18 ^
        b27 & b59 ^
        b40 & b48 ^
        b61 & b65 ^
        b68 & b84;
  }

  /// Get output from linear function f(x).
  ///
  /// @return Output from LFSR.
  int getOutputLFSR() {
    var s0 = lfsr[0];
    var s7 = lfsr[0] >>> 7 | lfsr[1] << 25;
    var s38 = lfsr[1] >>> 6 | lfsr[2] << 26;
    var s70 = lfsr[2] >>> 6 | lfsr[3] << 26;
    var s81 = lfsr[2] >>> 17 | lfsr[3] << 15;
    var s96 = lfsr[3];

    return s0 ^ s7 ^ s38 ^ s70 ^ s81 ^ s96;
  }

  /// Get output from output function h(x).
  ///
  /// @return Output from h(x).
  int getOutput() {
    var b2 = nfsr[0] >>> 2 | nfsr[1] << 30;
    var b12 = nfsr[0] >>> 12 | nfsr[1] << 20;
    var b15 = nfsr[0] >>> 15 | nfsr[1] << 17;
    var b36 = nfsr[1] >>> 4 | nfsr[2] << 28;
    var b45 = nfsr[1] >>> 13 | nfsr[2] << 19;
    var b64 = nfsr[2];
    var b73 = nfsr[2] >>> 9 | nfsr[3] << 23;
    var b89 = nfsr[2] >>> 25 | nfsr[3] << 7;
    var b95 = nfsr[2] >>> 31 | nfsr[3] << 1;
    var s8 = lfsr[0] >>> 8 | lfsr[1] << 24;
    var s13 = lfsr[0] >>> 13 | lfsr[1] << 19;
    var s20 = lfsr[0] >>> 20 | lfsr[1] << 12;
    var s42 = lfsr[1] >>> 10 | lfsr[2] << 22;
    var s60 = lfsr[1] >>> 28 | lfsr[2] << 4;
    var s79 = lfsr[2] >>> 15 | lfsr[3] << 17;
    var s93 = lfsr[2] >>> 29 | lfsr[3] << 3;
    var s94 = lfsr[2] >>> 31 | lfsr[3] << 1;

    return b12 & s8 ^
        s13 & s20 ^
        b95 & s42 ^
        s60 & s79 ^
        b12 & b95 & s94 ^
        s93 ^
        b2 ^
        b15 ^
        b36 ^
        b45 ^
        b64 ^
        b73 ^
        b89;
  }

  /// Shift array 32 bits and add val to index.length - 1.
  ///
  /// @param array The array to shift.
  /// @param val   The value to shift in.
  /// @return The shifted array with val added to index.length - 1.
  List<int> shift(List<int> array, int val) {
    array[0] = array[1];
    array[1] = array[2];
    array[2] = array[3];
    array[3] = val;

    return array;
  }

  /// Set keys, reset cipher.
  ///
  /// @param keyBytes The key.
  /// @param ivBytes  The IV.
  void setKey(Uint8List keyBytes, Uint8List ivBytes) {
    ivBytes[12] = 0xFF;
    ivBytes[13] = 0xFF;
    ivBytes[14] = 0xFF;
    ivBytes[15] = 0xFF;
    workingKey = keyBytes;
    workingIV = ivBytes;

    /**
   * Load NFSR and LFSR
   */
    var j = 0;
    for (var i = 0; i < nfsr.length; i++) {
      nfsr[i] = ((workingKey[j + 3]) << 24) |
          ((workingKey[j + 2]) << 16) & 0x00FF0000 |
          ((workingKey[j + 1]) << 8) & 0x0000FF00 |
          ((workingKey[j]) & 0x000000FF);

      lfsr[i] = ((workingIV[j + 3]) << 24) |
          ((workingIV[j + 2]) << 16) & 0x00FF0000 |
          ((workingIV[j + 1]) << 8) & 0x0000FF00 |
          ((workingIV[j]) & 0x000000FF);
      j += 4;
    }
  }

  @override
  int processBytes(
      Uint8List _in, int inOff, int len, Uint8List out, int outOff) {
    if (!initialised) {
      throw StateError(algorithmName + ' not initialised');
    }

    if ((inOff + len) > _in.length) {
      throw ArgumentError('input buffer too short');
    }

    if ((outOff + len) > out.length) {
      throw ArgumentError('output buffer too short');
    }

    for (var i = 0; i < len; i++) {
      out[outOff + i] = (_in[inOff + i] ^ getKeyStream());
    }

    return len;
  }

  @override
  void reset() {
    index = 4;
    setKey(workingKey, workingIV);
    initGrain();
  }

  /// Run Grain one round(i.e. 32 bits).
  void oneRound() {
    output = getOutput();
    out[0] = output;
    out[1] = (output >> 8);
    out[2] = (output >> 16);
    out[3] = (output >> 24);

    nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]);
    lfsr = shift(lfsr, getOutputLFSR());
  }

  @override
  int returnByte(int _in) {
    if (!initialised) {
      throw StateError(algorithmName + ' not initialised');
    }
    return _in ^ getKeyStream();
  }

  int getKeyStream() {
    if (index > 3) {
      oneRound();
      index = 0;
    }
    return out[index++];
  }

  @override
  Uint8List process(Uint8List data) {
    // TODO: implement process
    throw UnimplementedError();
  }
}

redDwarf03 avatar Mar 09 '23 22:03 redDwarf03