2018-10-18 17:10:29 +09:00
|
|
|
[](https://godoc.org/github.com/opencoff/go-sign)
|
|
|
|
|
|
|
|
# README for sigtool
|
|
|
|
|
|
|
|
|
|
|
|
## What is this?
|
2019-10-17 14:29:01 -07:00
|
|
|
`sigtool` is an opinionated tool to generate keys, sign, verify, encrypt &
|
|
|
|
decrypt files using Ed25519 signature scheme. In many ways, it is like
|
|
|
|
like OpenBSD's [signify][1] -- except written in Golang and definitely
|
|
|
|
easier to use.
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
It can sign and verify very large files - it prehashes the files
|
2019-10-17 14:29:01 -07:00
|
|
|
with SHA-512 and then signs the SHA-512 checksum. The keys and signatures
|
|
|
|
are YAML files and so, human readable.
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
It can encrypt files for multiple recipients - each of whom is identified
|
|
|
|
by their Ed25519 public key. The encryption by default generates ephmeral
|
|
|
|
Curve25519 keys and creates pair-wise shared secret for each recipient of
|
|
|
|
the encrypted file. The caller can optionally use a specific secret key
|
|
|
|
during the encryption process - this has the benefit of also authenticating
|
|
|
|
the sender (and the receiver can verify the sender if they possess the
|
|
|
|
corresponding public key).
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
The sign, verify, encrypt, decrypt operations can use OpenSSH Ed25519 keys
|
|
|
|
*or* the keys generated by sigtool. This means, you can send encrypted
|
|
|
|
files to any recipient identified by their comment in `~/.ssh/authorized_keys`.
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
## How do I build it?
|
|
|
|
With Go 1.5 and later:
|
|
|
|
|
|
|
|
git clone https://github.com/opencoff/sigtool
|
|
|
|
cd sigtool
|
|
|
|
make
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
The binary will be in `./bin/$HOSTOS-$ARCH/sigtool`.
|
|
|
|
where `$HOSTOS` is the host OS where you are building (e.g., openbsd)
|
|
|
|
and `$ARCH` is the CPU architecture (e.g., amd64).
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
## How do I use it?
|
|
|
|
Broadly, the tool can:
|
|
|
|
|
|
|
|
- generate new key pairs (public key and private key)
|
|
|
|
- sign a file
|
|
|
|
- verify a file against its signature
|
2019-10-17 14:29:01 -07:00
|
|
|
- encrypt a file
|
|
|
|
- decrypt a file
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
### Generate Key pair
|
|
|
|
To start with, you generate a new key pair (a public key used for
|
|
|
|
verification and a private key used for signing). e.g.,
|
|
|
|
|
|
|
|
sigtool gen /tmp/testkey
|
|
|
|
|
|
|
|
The tool then generates */tmp/testkey.pub* and */tmp/testkey.key*. The secret
|
|
|
|
key (".key") can optionally be encrypted with a user supplied pass
|
|
|
|
phrase - which the user has to enter via interactive prompt:
|
|
|
|
|
|
|
|
sigtool gen -p /tmp/testkey
|
|
|
|
|
|
|
|
### Sign a file
|
|
|
|
Signing a file requires the user to provide a previously generated
|
|
|
|
Ed25519 private key. The signature (YAML) is written to STDOUT.
|
|
|
|
e.g., to sign `archive.tar.gz` with private key `/tmp/testkey.key`:
|
|
|
|
|
|
|
|
sigtool sign /tmp/testkey.key archive.tar.gz
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
If *testkey.key* was encrypted without a user pass phrase:
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
sigtool sign --no-password /tmp/testkey.key archive.tar.gz
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
|
|
|
|
The signature can also be written directly to a user supplied output
|
|
|
|
file.
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
sigtool sign -o archive.sig /tmp/testkey.key archive.tar.gz
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
|
|
|
|
### Verify a signature against a file
|
|
|
|
Verifying a signature of a file requires the user to supply three
|
|
|
|
pieces of information:
|
|
|
|
|
|
|
|
- the Ed25519 public key to be used for verification
|
|
|
|
- the Ed25519 signature
|
|
|
|
- the file whose signature must be verified
|
|
|
|
|
|
|
|
e.g., to verify the signature of *archive.tar.gz* against
|
|
|
|
*testkey.pub* using the signature *archive.sig*
|
|
|
|
|
|
|
|
sigtool verify /tmp/testkey.pub archive.sig archive.tar.gz
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
|
|
|
|
Note that signing and verifying can also work with OpenSSH ed25519
|
|
|
|
keys.
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
### Encrypt a file by authenticating the sender
|
|
|
|
If the sender wishes to prove to the recipient that they encrypted
|
|
|
|
a file:
|
|
|
|
|
2019-10-22 10:06:49 -07:00
|
|
|
sigtool encrypt -s sender.key to.pub -o archive.tar.gz.enc archive.tar.gz
|
2019-10-17 14:29:01 -07:00
|
|
|
|
|
|
|
|
|
|
|
This will create an encrypted file *archive.tar.gz.enc* such that the
|
2019-10-18 15:42:08 -07:00
|
|
|
recipient in possession of *to.key* can decrypt it. Furthermore, if
|
|
|
|
the recipient has *sender.pub*, they can verify that the sender is indeed
|
2019-10-17 14:29:01 -07:00
|
|
|
who they expect.
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
### Decrypt a file and verify the sender
|
|
|
|
If the receiver has the public key of the sender, they can verify that
|
|
|
|
they indeed sent the file by cryptographically checking the output:
|
|
|
|
|
2019-10-22 10:06:49 -07:00
|
|
|
sigtool decrypt -o archive.tar.gz -v sender.pub to.key archive.tar.gz.enc
|
2019-10-18 15:42:08 -07:00
|
|
|
|
|
|
|
Note that the verification is optional and if the `-v` option is not
|
|
|
|
used, then decryption will proceed without verifying the sender.
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
### Encrypt a file *without* authenticating the sender
|
2019-10-18 15:42:08 -07:00
|
|
|
`sigtool` can generate ephemeral keys for encrypting a file such that
|
|
|
|
the receiver doesn't need to authenticate the sender:
|
|
|
|
|
2019-10-22 10:06:49 -07:00
|
|
|
sigtool encrypt to.pub -o archive.tar.gz.enc archive.tar.gz
|
2019-10-18 15:42:08 -07:00
|
|
|
|
|
|
|
This will create an encrypted file *archive.tar.gz.enc* such that the
|
|
|
|
recipient in possession of *to.key* can decrypt it.
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
### Encrypt a file to an OpenSSH recipient *without* authenticating the sender
|
|
|
|
Suppose you want to send an encrypted file where the recipient's
|
|
|
|
public key is in `~/.ssh/authorized_keys`. Such a recipient is identified
|
|
|
|
by their OpenSSH key comment (typically `name@domain`):
|
|
|
|
|
|
|
|
sigtool encrypt user@domain -o archive.tar.gz.enc archive.tar.gz
|
|
|
|
|
|
|
|
If you have their public key in file "name-domain.pub", you can do:
|
|
|
|
|
|
|
|
sigtool encrypt name-domain.pub -o archive.tar.gz.enc archive.tar.gz
|
|
|
|
|
|
|
|
This will create an encrypted file *archive.tar.gz.enc* such that the
|
|
|
|
recipient can decrypt using their private key.
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
## Technical Details
|
2019-10-17 14:29:01 -07:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
### How is the private key protected?
|
2020-01-08 09:17:54 -08:00
|
|
|
The Ed25519 private key is encrypted in AES-GCM-256 mode using a key
|
|
|
|
derived from the user's pass phrase.
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
### How is the Encryption done?
|
|
|
|
The file encryption uses AES-GCM-256 in AEAD mode. The encryption uses
|
|
|
|
a random 32-byte AES-256 key. The input is broken into chunks and
|
|
|
|
each chunk is individually AEAD encrypted. The default chunk size
|
|
|
|
is 4MB (4 * 1048576 bytes). Each chunk generates its own nonce
|
|
|
|
from a global salt. The nonce is calculated as a SHA256 hash of
|
|
|
|
the salt, the chunk length and the block number.
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
### What is the public-key cryptography?
|
2019-10-18 15:42:08 -07:00
|
|
|
`sigtool` uses Curve25519 ECC to generate shared secrets between
|
|
|
|
pairs of sender & recipients. This pairwise shared secret is expanded
|
|
|
|
using HKDF to generate a key-encryption-key. The file-encryption key
|
|
|
|
is AEAD encrypted with this key-encryption-key. Thus, each recipient
|
|
|
|
has their own individual encrypted key blob.
|
|
|
|
|
2019-11-05 21:42:25 +01:00
|
|
|
The Ed25519 keys generated by `sigtool` are transformed to their
|
2019-10-18 15:42:08 -07:00
|
|
|
corresponding Curve25519 points in order to generate the shared secret.
|
|
|
|
This elliptic co-ordinate transform follows [FiloSottile's writeup][2].
|
|
|
|
|
|
|
|
### Format of the Encrypted File
|
2020-01-08 09:17:54 -08:00
|
|
|
Every encrypted file starts with a header and the header-checksum:
|
|
|
|
|
|
|
|
* Fixed-size header
|
|
|
|
* Variable-length header
|
|
|
|
* SHA256 sum of both of the above
|
|
|
|
|
|
|
|
The fixed length header is:
|
2019-10-18 15:42:08 -07:00
|
|
|
|
|
|
|
7 byte magic ("SigTool")
|
|
|
|
1 byte version number
|
|
|
|
4 byte header length (big endian encoding)
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
The variable length header has the per-recipient wrapped keys. This is
|
|
|
|
described as a protobuf file (sign/hdr.proto):
|
2019-10-18 15:42:08 -07:00
|
|
|
|
|
|
|
```protobuf
|
|
|
|
message header {
|
|
|
|
uint32 chunk_size = 1;
|
|
|
|
bytes salt = 2;
|
|
|
|
repeated wrapped_key keys = 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
message wrapped_key {
|
|
|
|
bytes pk_hash = 1; // hash of Ed25519 PK
|
|
|
|
bytes pk = 2; // curve25519 PK
|
|
|
|
bytes nonce = 3; // AEAD nonce
|
|
|
|
bytes key = 4; // AEAD encrypted key
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
The SHA256 sum covers the fixed-length and variable-length headers.
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
The encrypted data immediately follows the headers above. Each encrypted
|
|
|
|
chunk is encoded the same way:
|
|
|
|
|
|
|
|
```C
|
|
|
|
4 byte chunk length (big endian encoding)
|
|
|
|
chunk data
|
|
|
|
AEAD tag
|
|
|
|
```
|
|
|
|
|
|
|
|
The chunk data and AEAD tag are treated as an atomic unit for AEAD
|
|
|
|
decryption.
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
## Understanding the Code
|
|
|
|
`src/sign` is a library to generate, verify and store Ed25519 keys
|
|
|
|
and signatures. It uses the extended library (golang.org/x/crypto)
|
|
|
|
for the underlying operations.
|
|
|
|
|
2019-10-18 15:42:08 -07:00
|
|
|
`src/crypt.go` contains the encryption & decryption code.
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
The generated keys and signatures are proper YAML files and human
|
|
|
|
readable.
|
|
|
|
|
|
|
|
The signature file contains a hash of the public key - so that at
|
|
|
|
verification time, the right private key may be used (in situations
|
|
|
|
where there are lots of keys).
|
|
|
|
|
|
|
|
Signatures on large files are calculated efficiently by reading them
|
|
|
|
in memory mapped mode (```mmap(2)```) and hashing the file contents
|
|
|
|
using SHA-512. The Ed25519 signature is calculated on the file-hash.
|
|
|
|
|
|
|
|
## Example of Keys, Signature
|
|
|
|
|
|
|
|
### Ed25519 Public Key
|
|
|
|
A serialized Ed25519 public key looks like so:
|
|
|
|
|
|
|
|
pk: uxpDh+gqXojAmxA/6vxZHzA+Uk+8wogUwvEhPBlWgvo=
|
|
|
|
|
|
|
|
### Ed25519 Private Key
|
|
|
|
And, a serialized Ed25519 private key looks like so:
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
```yaml
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
esk: t3vfqHbgUiA733KKPymFjWT8DdnBEkiMfsDHolPUdQWpvVn/F1Z4J6KYV3M5rGO9xgKxh5RAmqt+6LKgOiJAMQ==
|
|
|
|
salt: pPHKG55UJYtJ5wU0G9hBvNQJ0DvT0a7T4Fmj4aPB84s=
|
|
|
|
algo: scrypt-sha256
|
|
|
|
Z: 131072
|
|
|
|
r: 16
|
|
|
|
p: 1
|
2019-10-17 14:29:01 -07:00
|
|
|
```
|
2018-10-18 17:10:29 +09:00
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
The Ed25519 private key is encrypted using AES-256-GCM AEAD mode;
|
|
|
|
the encryption key is derived from the user supplied passphrase
|
|
|
|
using scrypt KDF. A user supplied passphrase is first expanded
|
|
|
|
using SHA-512 before being used in ```scrypt()```. In pseudo code,
|
|
|
|
this operation looks like below:
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
passphrase = get_user_passphrase()
|
|
|
|
hpass = SHA512(passphrase)
|
|
|
|
salt = randombytes(32)
|
2020-01-08 09:17:54 -08:00
|
|
|
key = Scrypt(hpass, salt, N, r, p)
|
|
|
|
esk = AES256_GCM(ed25519_private_key, key)
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
Where, ```N```, ```r```, ```p``` are Scrypt parameters. In our
|
|
|
|
implementation:
|
|
|
|
|
2020-01-08 09:17:54 -08:00
|
|
|
N = 2^19 (1 << 19)
|
|
|
|
r = 8
|
2018-10-18 17:10:29 +09:00
|
|
|
p = 1
|
|
|
|
|
|
|
|
### Ed25519 Signature
|
|
|
|
A generated signature looks like below after serialization:
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
```yaml
|
|
|
|
|
2018-10-18 17:10:29 +09:00
|
|
|
comment: inpfile=/tmp/file.txt
|
|
|
|
pkhash: 36z9tCwTIVNwwDlExrB0SQ==
|
|
|
|
signature: ow2oBP+buDbEvlNakOrsxgB5Yc/7PYyPVZCkfyu7oahw8BakF4Qf32uswPaKGZ8RVz4uXboYHdZtfrEjCgP/Cg==
|
2019-10-17 14:29:01 -07:00
|
|
|
```
|
2018-10-18 17:10:29 +09:00
|
|
|
|
|
|
|
Here, ```pkhash`` is a SHA256 of the public key needed to verify
|
|
|
|
this signature.
|
|
|
|
|
|
|
|
## Licensing Terms
|
|
|
|
The tool and code is licensed under the terms of the
|
|
|
|
GNU Public License v2.0 (strictly v2.0). If you need a commercial
|
|
|
|
license or a different license, please get in touch with me.
|
|
|
|
|
|
|
|
See the file ``LICENSE.md`` for the full terms of the license.
|
|
|
|
|
|
|
|
## Author
|
|
|
|
Sudhi Herle <sw@herle.net>
|
|
|
|
|
2019-10-17 14:29:01 -07:00
|
|
|
[1]: https://www.openbsd.org/papers/bsdcan-signify.html
|
|
|
|
[2]: https://blog.filippo.io/using-ed25519-keys-for-encryption/
|