daniel is a fast password generator based on using a single master password. When generating a password, it produces a small "reminder" containing all the information necessary to reproduce the password, but only in conjunction with the master password. Without the master password, the reminder is useless.
Simply run the script from the command line:
% daniel Please enter your master password:
Enter a strong master password. This can actually be a passphrase: it can contain any text you like, as long as it’s in UTF-8. Be sure this is something you can remember, since your passwords cannot be recovered if you forget it.
Then daniel will print out a six-character checksum. This checksum is computed based on the master password, and it will likely be different if you accidentally mistype your master password. This helps prevent you from generating a lot of passwords based on a mistyped passphrase and then being unable to reproduce them.
daniel then prompts you for a code. This code is used to generate a unique password. One common technique is to use an abbreviated form of the domain name, so if you wanted to generate a password for GitHub, you could use “github.com” (without the quotes).
Finally, daniel produces the password and a reminder. If you run daniel with the reminder on the command line, it will print out the same password as before once you enter your master password.
% daniel Please enter your master password: # ok, checksum is 72eb36 Enter code: example.tld Password is: nj&xzO@hz&QvuoGY Reminder is: 72eb360a1000example.tld % daniel 72eb360a1000example.tld Please enter your master password: # ok, checksum is 72eb36 Password is: nj&xzO@hz&QvuoGY
The master password in this case was “foobar”. Please use something more secure.
Yes. The flags parameter can be adjusted on the command line with -f, or by
entering !flags=value
at the “Enter code:” prompt. When the flags are set
to 0, all characters from hex 20 to hex 7E are included. The different bits
mean:
Flag |
Meaning |
1 |
Exclude all digits |
2 |
Exclude spaces |
4 |
Exclude those characters on top of the digits on a US keyboard |
8 |
Exclude other symbols |
16 |
Exclude letters |
The default is 2 (exclude spaces) because many websites don’t handle those characters properly. The flags parameter is encoded as part of the reminder, and changing the flags will cause a completely different password to be generated.
Yes. The length can be adjusted using the -l
parameter, or by entering
!length=value
at the “Enter code:” prompt. The length is encoded as part
of the reminder, but it does not cause the generated password to be any
different other than in length. That is, truncating a 16-character password to
8 characters is exactly the same as generating an 8-character password with the
same parameters. The default length is 16.
There is a version field that can be incremented to produce a completely
different password. Use -v
or enter !version=value
at the “Enter code:”
prompt. The default version is 0, and values up to 232-1 are accepted. The
version is encoded as part of the reminder.
This feature was written with the corporate and academic worlds in mind, but it can also be used if you absolutely need one of a certain set of characters in your password, but the default one that was generated does not contain such a character. Simply bump the version number until a satisfactory password is generated.
No. You may store it completely publicly. Without the master password, it cannot be used to regenerate your password. Note, however, that if you choose to store reminders publicly, and they contain information about the site in question, you are disclosing to the public that you have an account on that site.
Yes. You can use the -m
flag to put daniel into existing password mode.
Instead of generating a new password, it will prompt you (twice) for an existing
password, and generate a reminder from that. These reminders are much longer
than the standard kind, however.
You should not use the same code for more than one password, as this will leak information about your password.
Yes. Install the clipboard gem and its dependencies and use the -p
option.
Note that on Linux, the xclip
utility is required unless you are using a
version of the clipboard gem newer than 1.0.5, as xsel
support is broken in
earlier versions.
Yes. You can use the -r
option to put daniel in machine-readable mode.
Instead of the normal output, daniel will print a number of lines each beginning
with a colon. If the line is a prompt, it will end with a question mark. If
the line is an error, it will end with an exclamation point. Otherwise, after
the message type, a single space and a URL-encoded value will be output. This
ensures that all characters are represented properly and that a non-prompt line
can never end in a question mark.
daniel can be loaded with Kernel#load, or with Kernel#require if you use the version in the lib directory (since it has an .rb extension). The tests use this technique.
It’s also designed to be a single, standalone script so that people can copy it wherever they might need it. I will probably make it a gem at some point, though.
Ruby provides all the necessary cryptographic primitives as part of the standard library. Other languages, such as Perl, do not.
The code should run on MRI 1.8.7, 1.9.3, 2.0.0, 2.1, 2.2, 2.3, 2.4, and 2.5. 1.8 will require the io-console gem for interactive use; this is part of the standard library in 1.9.3.
JRuby 1.7 works fine with the jruby-openssl gem. Other versions haven’t been tested, but are expected to work. At least in theory, there’s no reason it shouldn’t function just fine on Rubinius as well.
Opal can be used, provided you copy or symlink the files from the core
directory of the Stanford JavaScript Crypto Library (sjcl) into
lib/daniel/opal
. Due to a large portion of the Ruby standard library being
unavailable in Opal, daniel can only be used as a library with it; no main
program is available.
Having said that, only MRI 1.9.3, 2.0.0, 2.1, 2.2, and 2.3 are officially supported. I will probably notice if the tests fail on 1.8.7 and fix it myself. You’re welcome to send a pull request if it doesn’t work on your preferred flavor (although please stick to 1.8.7-equivalent or newer).
Glad you asked. Essentially, the master password is hashed with some static data using 1024 iterations of PBKDF-2 using HMAC-SHA256. No salt is used, since there is no place to store the salt. This produces the master secret.
The checksum is generated from the first three bytes, hex-encoded, of the SHA-256 hash of the master secret and some static data. This is done from the master secret and not the master password because it forces an attacker to go through the PBKDF-2 step in order to generate candidate matches.
Now things differ.
The code, flags, password version, and some static data are hashed using PBKDF-2 again, this time with the master secret as the salt. This produces the initialization vector.
To generate the password, AES-256 in counter mode is used as a byte generator, with the master secret as the key and the first 16 bytes of the initialization vector, as, well, the initialization vector. If a generated byte is in the set of acceptable values, it is output; otherwise, it is discarded.
For existing password mode, the byte generator is run the same way, except that the first n bytes (where n is the existing password length), regardless of value, are XORed with the existing password to generate a mask, which is encoded in the reminder.
The master secret is run through an additional, variable number of iterations of PBKDF2/HMAC-SHA256 (default of 1024), with an additional salt (if provided). This produces the master key.
The master key is used with HKDF-Expand (using SHA-256) to produce a 256-bit seed and 256-bit MAC key.
The flags, version, and code are serialized into a canonical JSON format, and hashed with SHA-256. An HMAC-DRBG instance using SHA-256 is created using the seed and the hash of the JSON parameters. 1024 bytes are generated at a time, and the password or mask is generated from these bytes, just as the AES output is used for version 0.
The data for a version 1 reminder is encoded as a JWT with a prefix and suffix. The MAC key is used to protect the contents of this JWT using the HS256 algorithm.
The version 1 algorithm was designed to improve on defects in version 0 (such as the lack of salt, a fixed number of iterations, and unprotected parameters). It also has the benefit that it can be easily implemented without support for cryptographic operations other than SHA-256 and HMAC, in case of export concerns.
Version 1 reminders also have the ability to use a checksum of all-zeros, which indicates that no checksum comparison is to be done. This prevents leaking any information about the master password, except by checking of the MAC.