Passwords

In Java based applications, passing passwords as String objects has the disadvantage that you have to rely on garbage collection to clean up once they are no longer used. For that reason, char[] is the preferred method for dealing with passwords. Once a password is no longer used, the character array can simply be overwritten to remove the sensitive data from memory.

Passphrase

PGPainless uses a wrapper class Passphrase, which takes care for the wiping of unused passwords:

Passphrase passphrase = new Passphrase(new char[] {'h', 'e', 'l', 'l', 'o'});
assertTrue(passphrase.isValid());

assertArrayEquals(new char[] {'h', 'e', 'l', 'l', 'o'}, passphrase.getChars()):

// Once we are done, we can clean the data
passphrase.clear();

assertFalse(passphrase.isValid());
assertNull(passphrase.getChars());

Furthermore, Passphrase can also wrap empty passphrases, which increases null-safety of the API:

Passphrase empty = Passphrase.emptyPassphrase();
assertTrue(empty.isValid());
assertTrue(empty.isEmpty());
assertNull(empty.getChars());

empty.clear();

assertFalse(empty.isValid());

SecretKeyRingProtector

There are certain operations that require you to provide the passphrase for a key. Examples are decryption of messages, or creating signatures / certifications.

The primary way of telling PGPainless, which password to use for a certain key is the SecretKeyRingProtector interface which maps Passphrases to (sub-)keys. There are multiple implementations of this interface, which may or may not suite your needs:

// If your key is not password protected, this implementation is for you:
SecretKeyRingProtector unprotected = SecretKeyRingProtector
        .unprotectedKeys();

// If you use a single passphrase for all (sub-) keys, take this:
SecretKeyRingProtector singlePassphrase = SecretKeyRingProtector
        .unlockAnyKeyWith(passphrase);

// If you want to be flexible, use this:
CachingSecretKeyRingProtector flexible = SecretKeyRingProtector
        .defaultSecretKeyRingProtector(passphraseCallback);

SecretKeyRingProtector.unprotectedKeys() will return an empty passphrase for any key. It is best used when dealing with unencrypted secret keys.

SecretKeyRingProtector.unlockAnyKeyWith(passphrase) will return the same exact passphrase for any given key. You should use this if you have a single key with a static passphrase.

The last example shows how to instantiate the CachingSecretKeyRingProtector with a SecretKeyPassphraseProvider as argument. As the name suggests, the CachingSecretKeyRingProtector caches passphrases it knows about in a map. That way, you only have to provide the passphrase for a certain key only once, after which it will be remembered. If you try to unlock a protected secret key for which no passphrase is cached, the getPassphraseFor() method of the SecretKeyPassphraseProvider callback will be called to interactively ask for the missing passphrase. Afterwards, the acquired passphrase will be cached for future use.

Note

While especially the CachingSecretKeyRingProtector can handle multiple keys without problems, it is advised to use individual SecretKeyRingProtector objects per key. The reason for this is, that internally the 64bit key-id is used to resolve Passphrase objects and collisions are not unlikely in this key-space. Furthermore, multiple OpenPGP keys could contain the same subkey, but with different passphrases set. If the same SecretKeyRingProtector is used for two OpenPGP keys with the same subkey, but different passwords, the key-id collision will cause the password to be overwritten for one of the keys, which might result in issues. See FLO-04-004 WP2 of the 2021 security audit for more details.

Most SecretKeyRingProtector implementations can be instantiated with custom KeyRingProtectionSettings. By default, most implementations use KeyRingProtectionSettings.secureDefaultSettings() which corresponds to iterated and salted S2K using AES256 and SHA256 with an iteration count of 65536.