firstfloorsoftware / flutter_sodium

Flutter bindings for libsodium
BSD 3-Clause "New" or "Revised" License
102 stars 47 forks source link

Argon2id in sensitive mode gives SodiumException crypto_pwhash failed with -1 #65

Open java-crypto opened 3 years ago

java-crypto commented 3 years ago

I'm using Flutter_Sodium version 0.2.0 and tried to generate an Argon2id13 password hash using the sensitive parameter set.

My system parameter were:

Flutter 2.2.1
Dart 2.13.1
Sodium version: 1.0.18
Android 11: sdk_gphone_x86_arm-userdebug 11 RSR1.201013.001 6903271 dev-keys

This is the code I used to generate the hash (the full code follows at the end):

printC('Generate a 32 byte long encryption key with Argon2id in sensitive mode');
printC('Sodium version: ' + Sodium.versionString);
printC('Dart version: ' + Platform.version);
final passphrase = 'secret passphrase';
printC('passphrase: ' + passphrase);
var salt16Byte = generateSalt16Byte();
printC('salt (Base64): ' + base64Encoding(salt16Byte));
final outlen = 32;
final passwd = utf8.encoder.convert(passphrase);
final opslimit = Sodium.cryptoPwhashOpslimitSensitive;
final memlimit = Sodium.cryptoPwhashMemlimitSensitive;
final alg = Sodium.cryptoPwhashAlgArgon2id13;
printC('opsLimit: ' + Sodium.cryptoPwhashOpslimitSensitive.toString());
printC('memLimit: ' + Sodium.cryptoPwhashMemlimitSensitive.toString());
final hash =
Sodium.cryptoPwhash(outlen, passwd, salt16Byte, opslimit, memlimit, alg);
printC('hash (Base64): ' + base64Encoding(hash));

I'm using the constants Sodium.cryptoPwhashOpslimitSensitive and Sodium.cryptoPwhashMemlimitSensitive for opsLimit and memLimit.

The code fails with an Exception caught by gesture: SodiumException crypto_pwhash failed with -1 (full stack see below:):

I/flutter ( 9661): Generate a 32 byte long encryption key with Argon2id in sensitive mode
I/flutter ( 9661): Sodium version: 1.0.18
I/flutter ( 9661): Dart version: 2.13.1 (stable) (Fri May 21 12:45:36 2021 +0200) on "android_ia32"
I/flutter ( 9661): passphrase: secret passphrase
I/flutter ( 9661): salt (Base64): tVcZQuWbpNxGrvJ45H5Q8Q==
I/flutter ( 9661): opsLimit: 4
I/flutter ( 9661): memLimit: 1073741824

======== Exception caught by gesture ===============================================================
The following SodiumException object was thrown while handling a gesture:
  crypto_pwhash failed with -1

When the exception was thrown, this was the stack:
#0      Result.mustSucceed (package:flutter_sodium/src/extensions.dart:48:7)
#1      Sodium.cryptoPwhash (package:flutter_sodium/src/sodium.dart:1087:12)
#2      _MyWidgetState.runYourMainDartCode (package:dartprojectspointycastle/CrossPlatformCryptography/LibsodiumArgon2idSensitiveIssue.dart:136:12)
#3      _MyWidgetState.build.<anonymous closure> (package:dartprojectspointycastle/CrossPlatformCryptography/LibsodiumArgon2idSensitiveIssue.dart:82:21)
#4      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#87d41
  debugOwner: GestureDetector
  state: possible
  won arena
  finalPosition: Offset(300.3, 727.6)
  finalLocalPosition: Offset(35.2, 10.3)
  button: 1
  sent tap down
====================================================================================================

full code (it's a simple console app, just press "run the code"):

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_sodium/flutter_sodium.dart';

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Console'),
        ),
        body: MyWidget(),
      ),
    );
  }
}

// widget class
class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  // state variable
  String _textString = 'press the button "run the code"';
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          'console output',
          style: TextStyle(fontSize: 30),
        ),
        Expanded(
          flex: 1,
          child: new SingleChildScrollView(
            scrollDirection: Axis.vertical,
            child: Padding(
                padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
                child: Text(_textString,
                    style: TextStyle(
                      fontSize: 20.0,
                      fontWeight: FontWeight.bold,
                      fontFamily: 'Courier',
                      color: Colors.black,
                    ))),
          ),
        ),
        Container(
          child: Row(
            children: <Widget>[
              SizedBox(width: 10),
              Expanded(
                child: ElevatedButton(
                  child: Text('clear console'),
                  onPressed: () {
                    clearConsole();
                  },
                ),
              ),
              SizedBox(width: 10),
              Expanded(
                child: ElevatedButton(
                  child: Text('extra Button'),
                  onPressed: () {
                    runYourSecondDartCode();
                  },
                ),
              ),
              SizedBox(width: 10),
              Expanded(
                child: ElevatedButton(
                  child: Text('run the code'),
                  onPressed: () {
                    runYourMainDartCode();
                  },
                ),
              ),
              SizedBox(width: 10),
            ],
          ),
        ),
      ],
    );
  }

  void clearConsole() {
    setState(() {
      _textString = ''; // will add additional lines
    });
  }

  void printC(_newString) {
    setState(() {
      _textString =
          _textString + _newString + '\n';
    });
    print(_newString); // extra output on Console
  }
  /* ### instructions ###
      place your code inside runYourMainDartCode and print it to the console
      using printC('your output to the console');
      clearConsole() clears the actual console
      place your code that needs to be executed additionally inside
      runYourSecondDartCode and start it with "extra Button"
   */
  void runYourMainDartCode() {

    clearConsole();
    printC('Generate a 32 byte long encryption key with Argon2id in sensitive mode');

    printC('Sodium version: ' + Sodium.versionString);
    printC('Dart version: ' + Platform.version);

    final passphrase = 'secret passphrase';
    printC('passphrase: ' + passphrase);

    var salt16Byte = generateSalt16Byte();
    printC('salt (Base64): ' + base64Encoding(salt16Byte));

    final outlen = 32;
    final passwd = utf8.encoder.convert(passphrase);
    final opslimit = Sodium.cryptoPwhashOpslimitSensitive;
    final memlimit = Sodium.cryptoPwhashMemlimitSensitive;
    final alg = Sodium.cryptoPwhashAlgArgon2id13;
    printC('opsLimit: ' + Sodium.cryptoPwhashOpslimitSensitive.toString());
    printC('memLimit: ' + Sodium.cryptoPwhashMemlimitSensitive.toString());
    final hash =
    Sodium.cryptoPwhash(outlen, passwd, salt16Byte, opslimit, memlimit, alg);
    printC('hash (Base64): ' + base64Encoding(hash));
  }

  Uint8List generateSalt16Byte() {
    return Sodium.randombytesBuf(16);
  }

  String base64Encoding(Uint8List input) {
    return base64.encode(input);
  }

  void runYourSecondDartCode() {
    printC('execute additional code');
  }
}
kozw commented 3 years ago

You probably exceed the memory limitations of the OS. I believe the sensitive parameter uses 1Gb of memory, not sure where Android likes that. Try a more moderate parameter set.

java-crypto commented 3 years ago

@kozw: Thanks for your comment. As I'm writing "cross platform cryptography" routines, I've checked the lower parameter sets against Java etc. and they are working as expected with equal results. The "sensitive" has of course the most resource expectations, but as the library is build for Android OS there should be (minimum) a warning or a "stopper" to avoid those errors.