Last active
October 5, 2017 12:03
-
-
Save bcremer/858e4a3c279b276751335dc38fc162c5 to your computer and use it in GitHub Desktop.
Sodium Secretbox with user provided password
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
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
Why are you doing this at this point and then once again in the encrypt/decrypt method?