4.1 KiB
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.