Skip to content
This repository has been archived by the owner on Mar 16, 2023. It is now read-only.

Commit

Permalink
rewrote encryption schemes to not be reliant on 3rd party JavaScript …
Browse files Browse the repository at this point in the history
…crypto or GPG
  • Loading branch information
yahesh committed Oct 29, 2019
1 parent 8163c20 commit 008d2ce
Show file tree
Hide file tree
Showing 48 changed files with 2,292 additions and 3,174 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,5 +1,6 @@
# do not publish live config file
config.php
config/*
!config/config.php.default

# do not publish macOS blergh
._*
Expand Down
16 changes: 16 additions & 0 deletions .htaccess
Expand Up @@ -2,6 +2,22 @@
RewriteEngine On
RewriteBase /

# prevent access to certain locations
RewriteRule ^\.git(\/.*)?$ - [R=404,L]
RewriteRule ^\.gitattributes$ - [R=404,L]
RewriteRule ^\.gitignore$ - [R=404,L]
RewriteRule ^\.htaccess$ - [R=404,L]
RewriteRule ^actions(\/.*)?$ - [R=404,L]
RewriteRule ^CHANGELOG\.md$ - [R=404,L]
RewriteRule ^config(\/.*)?$ - [R=404,L]
RewriteRule ^ENCRYPTION\.md$ - [R=404,L]
RewriteRule ^lib(\/.*)?$ - [R=404,L]
RewriteRule ^LICENSE$ - [R=404,L]
RewriteRule ^pages(\/)?$ - [R=404,L]
RewriteRule ^README\.md$ - [R=404,L]
RewriteRule ^template(\/.*)?$ - [R=404,L]

# single entrypoint
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . /index.php [L]
</IfModule>
12 changes: 12 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,15 @@
# 0.20b0 (2019-10-18)

* rewrote the application to use OpenSSL instead of GPG fixing indirect integrity vulnerabilities
* rewrote the client-side encryption to be based on the [Web Cryptography API](https://www.w3.org/TR/WebCryptoAPI/)
* added `encryption.md` to describe the details of the newly implemented encryption scheme
* moved configuration file into the subfolder `config/`
* renamed folder `libs/` to `lib/`
* introduced the possibility to have key roll-overs (encrypt with new key but decrypt with new and old key)
* updated Bootstrap to version 3.4.1
* updated jQuery to version 3.4.1
* **Beware:** Decrypting old GPG-based URLs is not possible with this version. Due to the new encryption scheme v01 the first 48 bytes of a URL will be the same for all generated URLs therefore it is possible to distinguish with high certainty between old and new URLs so that it possible for you to redirect old URLs to a read-only instance.

# 0.14b0 (2019-10-28)

* introduced a read-only mode to simplify the migration to the upcoming release
Expand Down
138 changes: 138 additions & 0 deletions ENCRYPTION.md
@@ -0,0 +1,138 @@
# Shared-Secrets Encryption Schemes

Shared-Secrets uses two encryption schemes - one for the client-side encryption within the browser, one for the server-side encryption. The client-side encryption prevents the server from learning the actual secret while the server-side encryption prevents anyone else from being able to decrypt the secret. This way the read-once property of the secret sharing links can be enforced.

The encryption schemes are named after their version field (which is the first byte of every encrypted message). Currently schemes **v00** (client-side encryption) and **v01** (server-side encryption) are defined.

## Encryption Scheme v00

Encryption scheme v00 is a password-based Encrypt-then-MAC (EtM) scheme which uses AES-256-CTR as the encryption algorithm and HMAC-SHA-256 as the MAC algorithm. The key to derive the encryption key and the message authentication key is derived via a 10.000 rounds PBKDF2-SHA-256 on the password and a 256 bits long random salt.

### Message Format

Messages in the v00 format have the following format:

```
[version:01][salt:32][nonce:16][message:nn][mac:32]
```

### Message Fields

Messages in the v00 format have the following fields:

* **version** is 1 byte in size and **MUST** have the value `00h`
* **salt** is 32 bytes in size and **SHOULD** contain a cryptographically secure random number
* **nonce** is 16 bytes in size and **SHOULD** contain the UNIX timestamp as the first 8 bytes and zero bytes as the second 8 bytes
* **message** is the AES-256-CTR encrypted message
* **mac** is 32 bytes in size and **MUST** contain the HMAC-SHA-256 MAC of all previous fields in their given order

### Key Derivation

Messages in the v00 format use the following keys:

* **salt** is a cryptographically secure random number
* **key** is derived from the given password and **salt** using a 10.000 rounds PBKDF2-SHA-256
* **enckey** is derived from **key** as the key and the string `enc` as the message using HMAC-SHA-256
* **mackey** is derived from **key** as the key and the string `mac` as the message using HMAC-SHA-256

### Key Usage

Keys in the v00 format have the following purposes:

* **enckey** in combination with **nonce** are used to encrypt the message using AES-256-CTR
* **mackey** is used as the key to calculate the MAC of the message `[version:01][salt:32][nonce:16][message:nn]` using HMAC-SHA-256

### Example

The following Bash command encrypts a message with a given password using the above encryption scheme v00. A current version of OpenSSL/LibreSSL and the tool [nettle-pbkdf2](http://manpages.ubuntu.com/manpages/en/man1/nettle-pbkdf2.1.html) are needed:

```
# version 00 symmetric encryption
MESSAGE="message to encrypt" &&
PASSWORD="password" &&
VERSION="00" &&
NONCE=$(printf "%016x0000000000000000" "$(date +%s)") &&
SALT=$(openssl rand -hex 32) &&
KEY=$(echo -n "$PASSWORD" | nettle-pbkdf2 -i 10000 -l 32 --raw --hex-salt "$SALT" | xxd -p | tr -d "\n") &&
ENCKEY=$(echo -n "enc" | openssl dgst -sha256 -mac "HMAC" -macopt "hexkey:$KEY" -binary | xxd -p | tr -d "\n") &&
MACKEY=$(echo -n "mac" | openssl dgst -sha256 -mac "HMAC" -macopt "hexkey:$KEY" -binary | xxd -p | tr -d "\n") &&
ENCMESSAGE=$(echo -n "$MESSAGE" | openssl enc -aes-256-ctr -K "$ENCKEY" -iv "$NONCE" -nopad | xxd -p | tr -d "\n") &&
MACMESSAGE="$VERSION$SALT$NONCE$ENCMESSAGE" &&
MAC=$(echo -n "$MACMESSAGE" | xxd -r -p | openssl dgst -sha256 -mac "HMAC" -macopt "hexkey:$MACKEY" -binary | xxd -p | tr -d "\n") &&
FULLMESSAGE="$MACMESSAGE$MAC" &&
echo "$FULLMESSAGE"
```

## Encryption Scheme v01

Encryption scheme v01 is a key-based Encrypt-then-MAC (EtM) scheme which uses AES-256-CTR as the encryption algorithm and HMAC-SHA-256 as the MAC algorithm. The key to derive the encryption key and the message authentication key is randomly generated.

### Message Format

Messages in the v01 format have the following format:

```
[version:01][rsakeycount:02][rsakeyid:32][rsakeylength:02][rsakey:mm][...][rsakeyid:32][rsakeylength:02][rsakey:mm][nonce:16][message:nn][mac:32]
```

### Message Fields

Messages in the v01 format have the following fields:

* **version** is 1 byte in size and **MUST** have the value `01h`
* **rsakeycount** is 2 bytes in size and **MUST** denote the number of upcoming RSA key blocks
* **rsakeyid** is 32 bytes in size and **MUST** contain the SHA-256 hash of the DER-encoded RSA public key that was used to encrypt the upcoming RSA key
* **rsakeylength** is 2 bytes in size and **MUST** denote the length of the upcoming RSA key
* **rsakey** has the length of the previous **rsakeylength** field and **MUST** contain the RSA-encrypted key that was used to derive the encryption and message autentication key for the RSA key denoted by the previous **rsakeyid** field
* **nonce** is 16 bytes in size and **SHOULD** contain the UNIX timestamp as the first 8 bytes and zero bytes as the second 8 bytes
* **message** is the AES-256-CTR encrypted message
* **mac** is 32 bytes in size and **MUST** contain the HMAC-SHA-256 MAC of all previous fields in their given order

### Key Derivation

Messages in the v01 format use the following keys:

* **key** is cryptographically secure random number
* **enckey** is derived from **key** as the key and the string `enc` as the message using HMAC-SHA-256
* **mackey** is derived from **key** as the key and the string `mac` as the message using HMAC-SHA-256
* **rsakey** is derived by RSA-encrypting **key** with an RSA public key

The required RSA public key can be generated as follows:

```
openssl genrsa -out ./rsa.priv 2048
openssl rsa -in ./rsa.priv -pubout -outform PEM > ./rsa.pub
```

### Key Usage

Keys in the v00 format have the following purposes:

* **enckey** in combination with **nonce** are used to encrypt the message using AES-256-CTR
* **mackey** is used as the key to calculate the MAC of the message `[version:01][rsakeycount:02][rsakeyid:32][rsakeylength:02][rsakey:mm][...][rsakeyid:32][rsakeylength:02][rsakey:mm][nonce:16][message:nn]` using HMAC-SHA-256

### Example

The following Bash command encrypts a message with a given password using the above encryption scheme v01. A current version of OpenSSL/LibreSSL is needed:

```
# version 01 hybrid encryption
MESSAGE="message to encrypt" &&
RSAKEYFILE="./rsa.pub" &&
URLPREFIX="https://secrets.syseleven.de/" &&
RSAKEYCOUNT="0001" &&
VERSION="01" &&
NONCE=$(printf "%016x0000000000000000" "$(date +%s)") &&
KEY=$(openssl rand -hex 32) &&
ENCKEY=$(echo -n "enc" | openssl dgst -sha256 -mac "HMAC" -macopt "hexkey:$KEY" -binary | xxd -p | tr -d "\n") &&
MACKEY=$(echo -n "mac" | openssl dgst -sha256 -mac "HMAC" -macopt "hexkey:$KEY" -binary | xxd -p | tr -d "\n") &&
RSAKEY=$(echo -n "$KEY" | xxd -r -p | openssl rsautl -encrypt -oaep -pubin -inkey "$RSAKEYFILE" -keyform PEM | xxd -p | tr -d "\n") &&
RSAKEYID=$(openssl rsa -pubin -in "$RSAKEYFILE" -pubout -outform DER 2>/dev/null | openssl dgst -sha256 -binary | xxd -p | tr -d "\n") &&
RSAKEYLENGTH=$(echo -n "$RSAKEY" | xxd -r -p | wc -c) &&
RSAKEYLENGTH=$(printf "%04x" "$RSAKEYLENGTH") &&
ENCMESSAGE=$(echo -n "$MESSAGE" | openssl enc -aes-256-ctr -K "$ENCKEY" -iv "$NONCE" -nopad | xxd -p | tr -d "\n") &&
MACMESSAGE="$VERSION$RSAKEYCOUNT$RSAKEYID$RSAKEYLENGTH$RSAKEY$NONCE$ENCMESSAGE" &&
MAC=$(echo -n "$MACMESSAGE" | xxd -r -p | openssl dgst -sha256 -mac "HMAC" -macopt "hexkey:$MACKEY" -binary | xxd -p | tr -d "\n") &&
FULLMESSAGE="$MACMESSAGE$MAC" &&
echo "FULLMESSAGE"
```
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,4 +1,4 @@
Copyright (c) 2016, SysEleven GmbH
Copyright (c) 2016-2019, SysEleven GmbH
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
53 changes: 22 additions & 31 deletions README.md
Expand Up @@ -12,7 +12,7 @@ To protect your secret from getting known by the server or an attacker, you can

### Share a Secret

Simply enter your secret on the default page of the Shared-Secrets service. You can decide to password-protect the entered secret before sending it to the server by checking the "Password-protected:" box, entering your password and pressing the "Protect!" button. After that, press the "Share the Secret!" button. The secret will be GPG-encrypted and converted into a secret sharing link.
Simply enter your secret on the default page of the Shared-Secrets service. You can decide to password-protect the entered secret before sending it to the server by checking the "Password-protected:" box, entering your password and pressing the "Protect!" button. After that, press the "Share the Secret!" button. The secret will be encrypted and converted into a secret sharing link.

Secret sharing links can also be created by using a simple POST request:
```
Expand All @@ -33,7 +33,7 @@ curl -X POST -d "plain" <secret sharing link>

### Requirements

Shared-Secrets is based on MariaDB 10.0, Nginx 1.10 and PHP 7.0, but should also work with MySQL, Apache and earlier versions of PHP. GPG encryption is supported by directly calling the gpg binary. Previously, using the [GnuPG PECL package](https://pecl.php.net/package/gnupg) has been prefered mechanism due to its cleaner interface - however this interface is currently forcefully deactivated due to security concerns.
Shared-Secrets is based on MariaDB 10.0, Nginx 1.10 and PHP 7.0, but should also work with MySQL, Apache and earlier versions of PHP. Encrypted is done via the OpenSSL integration of PHP.

### Nginx Setup

Expand Down Expand Up @@ -61,6 +61,21 @@ server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
# prevent access to certain locations
location ~ ^\/\.git(\/.*)?$ { return 404; }
location ~ ^\/\.gitattributes$ { return 404; }
location ~ ^\/\.gitignore$ { return 404; }
location ~ ^\/\.htaccess$ { return 404; }
location ~ ^\/actions(\/.*)?$ { return 404; }
location ~ ^\/CHANGELOG\.md$ { return 404; }
location ~ ^\/config(\/.*)?$ { return 404; }
location ~ ^\/ENCRYPTION\.md$ { return 404; }
location ~ ^\/lib(\/.*)?$ { return 404; }
location ~ ^\/LICENSE$ { return 404; }
location ~ ^\/pages(\/.*)?$ { return 404; }
location ~ ^\/README\.md$ { return 404; }
location ~ ^\/template(\/.*)?$ { return 404; }
# Your configuration comes here:
# ...
}
Expand All @@ -82,37 +97,18 @@ add_header X-XSS-Protection "1; mode=block";

Shared-Secrets uses a single-table database to store who did retrieve which secret at what point in time. No actual secret content is stored. (The logging of IP addresses is disabled through the configuration parameter LOG_IP_ADDRESS by default.):
```
CREATE TABLE secrets ( fingerprint VARCHAR(64) PRIMARY KEY, ip VARCHAR(46), time TIMESTAMP );
```

### GPG Setup

You have to have a somewhat recent GPG version installed on the server. Furthermore you have to generate and import the private and public key of the service. It is recommended to generate the GPG keypair locally and only upload the key material that is really necessary. You can use a so-called notebook keyring (which does not include the certifying/primary private key) to reduce the risk of a full compromise of the used GPG keypair:
CREATE TABLE secrets ( fingerprint VARCHAR(64) PRIMARY KEY, time TIMESTAMP );
```
# generate a GPG keypair, set a passphrase so that you can export the private encryption subkey later
gpg --gen-key

# read the fingerprint of the generated key, replace <email> with the e-mail address that has been used for the key,
# the fingerprint will be shown with spaces for better readability - these have to be removed when configuring the software
gpg --with-fingerprint --list-keys <email>
### Encryption Setup

# export the private encryption subkey and the public key
gpg --export-secret-subkeys --armor <email> >./private.asc
gpg --export --armor <email> >./public.asc
You should generate a fresh RSA key pair with a minimum key size of 2048 bits:

# import the GPG keys on the server with the user that will execute GPG
sudo -u www-data -H gpg --import ./private.asc
sudo -u www-data -H gpg --import ./public.asc
```

Newer GPG versions disable stdin for key passphrases.

openssl genrsa -out ./rsa.key 2048
```
#in ~/.gnupg/gpg.conf (home of the user that will execute GPG) add:

use-agent
pinentry-mode loopback
```
**Beware:** You should place this file in a location so that it is not accessible through the webserver.

### Service Setup

Expand All @@ -124,11 +120,7 @@ It is strongly recommended to use TLS to protect the connection between the serv

## Attributions

* [asmCrypto](https://github.com/vibornoff/asmcrypto.js): for providing PBKDF2 and AES functions
* [Bootstrap](https://getbootstrap.com): for providing an easy-to-use framework to build nice-looking applications
* [buffer](https://github.com/feross/buffer): for providing Base64 encoding and array conversion functions
* [GnuPG](https://www.gnupg.org): for providing a reliable tool for secure communication
* [GnuPG PECL package](https://pecl.php.net/package/gnupg): for providing a clean interface to GnuPG (*currently not supported*)
* [html5shiv](https://github.com/aFarkas/html5shiv): for handling Internet Explorer compatibility stuff
* [jQuery](https://jquery.com): for just existing
* [Katharina Franz](https://www.katharinafranz.com): for suggesting Bootstrap as an easy-to-use framework to build nice-looking applications
Expand All @@ -137,7 +129,6 @@ It is strongly recommended to use TLS to protect the connection between the serv
## ToDo

* switch to a more personalized design (current design is taken from [here](https://github.com/twbs/bootstrap/tree/master/docs/examples/starter-template))
* implement an alternative encryption scheme based on AES instead of GPG (fewer dependencies)
* implement an expiry date functionality

## License
Expand Down
1 change: 0 additions & 1 deletion actions/how.php
Expand Up @@ -5,4 +5,3 @@

# dummy script without any content

?>
1 change: 0 additions & 1 deletion actions/imprint.php
Expand Up @@ -11,4 +11,3 @@ function redirect_page($target_url) {
header("Location: ".$target_url);
}

?>
22 changes: 22 additions & 0 deletions actions/pub.php
@@ -0,0 +1,22 @@
<?php

# prevent direct access
if (!defined("SYS11_SECRETS")) { die(""); }

function get_public_key() {
$result = null;

# for shared-secrets we only support encryption with one key
$keys = array_keys(RSA_PRIVATE_KEYS);
$pubkey = open_pubkey(RSA_PRIVATE_KEYS[$keys[count($keys)-1]]);
if (null !== $pubkey) {
try {
$result = get_keypem($pubkey);
} finally {
openssl_pkey_free($pubkey);
}
}

return $result;
}

0 comments on commit 008d2ce

Please sign in to comment.