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

The Haveno password not using a key derivation function makes it relatively vulnerable to brute forcing #1161

Open
phytohydra opened this issue Jul 21, 2024 · 3 comments
Labels

Comments

@phytohydra
Copy link
Contributor

monero-wallet-cli and monero-wallet-rpc both have a --kdf-rounds parameter and default to running the wallet password through a computationally intensive hash function, which greatly increases the time required to brute force it.

The Java keyring utility used by Haveno very quickly returns "Incorrect password" by comparison.

There isn't much point to using the default kdf rounds on the subsidiary wallets, which can take a long time to open if there are a lot of trade wallets, when the keyring password could just be brute-forced instead.

If the user uses the same password for Haveno that they do for other Monero wallets, the Haveno password will be the weak link that makes them all easier to brute force.

Since it's securing actual money and humans are bad at remembering high-entropy passwords, I think Haveno should add kdf hashing as well.

@maxz
Copy link

maxz commented Jul 21, 2024

KDF stands for Key Derivation Function, not Key Definition Function. It's deriving a key from your input. Not defining a key.

@phytohydra phytohydra changed the title The Haveno password not using a key definition function makes it relatively vulnerable to brute forcing The Haveno password not using a key derivation function makes it relatively vulnerable to brute forcing Jul 21, 2024
@phytohydra
Copy link
Contributor Author

phytohydra commented Jul 26, 2024

Comparative Analysis of Password Hashing Algorithms: Argon2, bcrypt, scrypt, and PBKDF2
PBKDF2 vs Argon2 - which is better?

Several years ago, Java only had PBKDF2; now the state-of-the-art Argon2 KDF is also available. We can use a pure Java implementation to run everywhere, and a native C library for maximum efficiency on systems that have it installed.

Argon2 Binding for the JVM

The Spring Framework wrapper class makes BouncyCastle's pure Java implementation pretty trivial to use:
https://github.com/phxql/argon2-playground/blob/main/src/main/java/de/mkammerer/argon2playground/Main.java

Note on running that 'playground' project: Its Maven pom.xml file isn't set up to produce an executable .jar file. Importing it into an IDE like NetBeans (using Team > Git > Clone) appears to be how it's intended to be run.

To build it as an executable .jar file:
Apache Maven Archiver - Set Up The Classpath

Add this <build> section to its pom.xml:

  <build>
   <plugins>
    <plugin>
     <!-- Build an executable JAR -->
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.1.0</version>
       <configuration>
        <archive>
         <manifest>
          <addClasspath>true</addClasspath>
          <classpathPrefix>${user.home}/.m2/repository/</classpathPrefix>
          <classpathLayoutType>repository</classpathLayoutType>
          <mainClass>de.mkammerer.argon2playground.Main</mainClass>
         </manifest>
        </archive>
       </configuration>
      </plugin>
     </plugins>
   </build>

@KewbitXMR
Copy link

KewbitXMR commented Aug 31, 2024

I've done something like this in Dart, sorry if it's not much use but it might give you an idea of how I'm encrypted the secure store / profobufs etc and seems to be working great:


import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:haveno_plus/services/secure_storage_service.dart';
import 'package:haveno_plus/utils/database_helper.dart';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/key_derivators/api.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/macs/hmac.dart';

class SecurityService {
  final SecureStorageService _secureStorage = SecureStorageService();
  final DatabaseHelper _databaseHelper = DatabaseHelper.instance;

  Future<void> setupUserPassword(String userPassword) async {
    final salt = _generateSalt();
    final hashedPassword = _hashPassword(userPassword, salt);
    final encryptedPassword = _encrypt('$salt:$hashedPassword', userPassword);
    await _secureStorage.writeUserPassword(encryptedPassword);
  }

  Future<bool> authenticateUserPassword(String inputPassword) async {
    final encryptedPassword = await _secureStorage.readUserPassword();
    if (encryptedPassword == null) {
      return false;
    }

    final decryptedPassword = _decrypt(encryptedPassword, inputPassword);
    if (decryptedPassword == null) {
      return false;
    }

    final parts = decryptedPassword.split(':');
    if (parts.length != 2) {
      return false;
    }

    final salt = parts[0];
    final storedHashedPassword = parts[1];
    final inputHashedPassword = _hashPassword(inputPassword, salt);
    return storedHashedPassword == inputHashedPassword;
  }

  String _generateSalt([int length = 16]) {
    final random = Random.secure();
    final saltBytes = List<int>.generate(length, (_) => random.nextInt(256));
    return base64Url.encode(saltBytes);
  }

  String _hashPassword(String password, String salt) {
    final saltBytes = base64Url.decode(salt);
    final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64))
      ..init(Pbkdf2Parameters(saltBytes, 10000, 32));
    final key = pbkdf2.process(utf8.encode(password));
    return base64Url.encode(key);
  }

  // Encrypts a value using AES
  String _encrypt(String value, String password) {
    final key = _deriveKey(password);
    final iv = _generateIV();
    final cipher = _initCipher(true, key, iv);
    final input = utf8.encode(value);
    final encrypted = cipher.process(input);
    final encryptedData = base64Url.encode(encrypted);
    final encodedIV = base64Url.encode(iv);
    return '$encodedIV:$encryptedData';
  }

  // Decrypts a value using AES
  String? _decrypt(String encryptedValue, String password) {
    try {
      final parts = encryptedValue.split(':');
      if (parts.length != 2) {
        return null;
      }
      final iv = base64Url.decode(parts[0]);
      final encryptedData = base64Url.decode(parts[1]);
      final key = _deriveKey(password);
      final cipher = _initCipher(false, key, iv);
      final decrypted = cipher.process(encryptedData);
      return utf8.decode(decrypted);
    } catch (e) {
      debugPrint('Decryption failed: $e');
      return null;
    }
  }

  // derives an AES key from the password using PBKDF2
  KeyParameter _deriveKey(String password, {int iterations = 10000, int keyLength = 32}) {
    final salt = utf8.encode('some_silly_salt'); // use fixed salt, no issue
    final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64))
      ..init(Pbkdf2Parameters(salt, iterations, keyLength));
    final key = pbkdf2.process(utf8.encode(password));
    return KeyParameter(key);
  }

  // denerates an AES cipher for encryption or decryption
  PaddedBlockCipher _initCipher(bool forEncryption, KeyParameter key, Uint8List iv) {
    final params = PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(
        ParametersWithIV<KeyParameter>(key, iv), null);
    final cipher = PaddedBlockCipher('AES/CBC/PKCS7');
    cipher.init(forEncryption, params);
    return cipher;
  }

  // Generates a random IV for AES encryption
  Uint8List _generateIV([int length = 16]) {
    final random = Random.secure();
    final iv = List<int>.generate(length, (_) => random.nextInt(256));
    return Uint8List.fromList(iv);
  }

  Future<void> resetAppData() async {
    // Wipe the secure storage
    await _secureStorage.storage.deleteAll();
    // Wipe the database
    await _databaseHelper.destroyDatabase();
  }
}

This devirives AES key from PBKDF2

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

No branches or pull requests

4 participants