Skip to content

Instantly share code, notes, and snippets.

@bcremer
Last active October 5, 2017 12:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bcremer/858e4a3c279b276751335dc38fc162c5 to your computer and use it in GitHub Desktop.
Save bcremer/858e4a3c279b276751335dc38fc162c5 to your computer and use it in GitHub Desktop.
Sodium Secretbox with user provided password
<?php
$password = 'My Plaintext Password';
$plaintext = 'This is my message';
$secretBox = new Secretbox();
$ciphertext = $secretBox->encrypt($plaintext, $password);
$decryptedPlaintext = $secretBox->decrypt($ciphertext, $password);
assertThat($decryptedPlaintext === $plaintext, 'Can decrypt');
assertThat(
$secretBox->encrypt($plaintext, $password) !== $secretBox->encrypt($plaintext, $password),
'Should not result in same ciphertext'
);
try {
$failed = false;
// modify ciphertext
$ciphertext[65] = 'f';
$secretBox->decrypt($ciphertext, $password);
} catch (\Exception $e) {
$failed = true;
} finally {
assertThat($failed, 'Tempered chiphertext should result in exception');
}
class Secretbox
{
public function encrypt(string $plaintext, string $password): string
{
// create a random salt for key derivation
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
$key = $this->deriveKeyFromUserPassword($password, $salt);
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
sodium_memzero($password);
sodium_memzero($key);
return $salt.$nonce.$ciphertext;
}
public function decrypt(string $ciphertext, string $password): string
{
$salt = substr($ciphertext, 0, SODIUM_CRYPTO_PWHASH_SALTBYTES);
$nonce = substr($ciphertext, SODIUM_CRYPTO_PWHASH_SALTBYTES, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($ciphertext, SODIUM_CRYPTO_PWHASH_SALTBYTES + SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$key = $this->deriveKeyFromUserPassword($password, $salt);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
sodium_memzero($password);
sodium_memzero($key);
sodium_memzero($nonce);
if ($plaintext === false) {
throw new \InvalidArgumentException('Bad ciphertext');
}
return $plaintext;
}
private function deriveKeyFromUserPassword(string $password, string $salt): string
{
$key = sodium_crypto_pwhash(
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
$password,
$salt,
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
sodium_memzero($password);
return $key;
}
}
function assertThat(bool $expressionResult, string $message) {
if (!$expressionResult) {
throw new \RuntimeException($message);
}
}
@tommy-muehle
Copy link

tommy-muehle commented Oct 5, 2017

Why are you doing this at this point and then once again in the encrypt/decrypt method?

@bcremer
Copy link
Author

bcremer commented Oct 5, 2017

Thank you @tommy-muehle. I am not sure if the calls to sodium_memzero are necessary at all. As far as I understand this method it frees the memory even before the garbage collector kicks in and thus cleans traces of this strings from memory as early as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment