PHP 8.4.0 RC4 available for testing

はじめに

Sodium は、 暗号化、復号化、署名、パスワードのハッシュ化その他の用途に使える、 モダンで使いやすいソフトウェアライブラリです。 より高レベルな暗号化ツールを作るための、 コアな操作を全て提供することを目標としています。

add a note

User Contributed Notes 6 notes

up
64
rafayhingoro at hotmail dot com
6 years ago
<?php
//Simple Usage

/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
*/
function safeEncrypt($message, $key)
{
$nonce = random_bytes(
SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
);

$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return
$cipher;
}

/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
*/
function safeDecrypt($encrypted, $key)
{
$decoded = base64_decode($encrypted);
if (
$decoded === false) {
throw new
Exception('Scream bloody murder, the encoding failed');
}
if (
mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
throw new
Exception('Scream bloody murder, the message was truncated');
}
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');

$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (
$plain === false) {
throw new
Exception('the message was tampered with in transit');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return
$plain;
}
//Encrypt & Decrypt your message
$key = sodium_crypto_secretbox_keygen();
$enc = safeEncrypt('Abdul Rafay Hingoro', $key); //generates random encrypted string (Base64 related)
echo $enc;
echo
'<br>';
$dec = safeDecrypt($enc, $key); //decrypts encoded string generated via safeEncrypt function
echo $dec;
?>

//Output
DEx9ATXEg/eRq8GWD3NT5BatB3m31WEDEYLK2V4L0Am5GZGoa2rvYWUpoUeCrm7W/pdgLJrNoE6AA8U=
Abdul Rafay Hingoro
up
8
Anonymous
6 years ago
Clearly there isn't much support or documentation for this yet.

Based off the same safeEncrypt implementation here and elsewhere, I updated it to work for me (I'm running libsodium 1.0.8).

I added these methods to a utility class.

<?php

// simple example
$message = 'My ultra-sensitive information';

$secret_key = Util::generateSecretKey();
$encrypted = Util::encrypt($message, $secret_key, 64);
$decrypted = Util::decrypt($encrypted, $secret_key, 64);

print
$decrypted;

// outputs 'My ultra-sensitive information'
// encrypted data is padded to 64-byte chunks to obfuscate actual data length

class Util
{
/**
* Get a secret key for encrypt/decrypt
*
* Use libsodium to generate a secret key. This should be kept secure.
*
* @return string
* @see encrypt(), decrypt()
*/
public static function generateSecretKey()
{
return
sodium_crypto_secretbox_keygen();
}

/**
* Encrypt a message
*
* Use libsodium to encrypt a string
*
* @param string $message - message to encrypt
* @param string $secret_key - encryption key
* @param int $block_size - pad the message by $block_size byte chunks to conceal encrypted data size. must match between encrypt/decrypt!
* @return string
* @see decrypt()
* @see https://github.com/jedisct1/libsodium/issues/392
*/
public static function encrypt($message, $secret_key, $block_size = 1)
{
// create a nonce for this operation. it will be stored and recovered in the message itself
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

// pad to $block_size byte chunks (enforce 512 byte limit)
$padded_message = sodium_pad($message, $block_size <= 512 ? $block_size : 512);

// encrypt message and combine with nonce
$cipher = base64_encode($nonce . sodium_crypto_secretbox($padded_message, $nonce, $secret_key));

// cleanup
sodium_memzero($message);
sodium_memzero($secret_key);

return
$cipher;
}

/**
* Decrypt a message
*
* Use libsodium to decrypt an encrypted string
*
* @param string $encrypted - the encrypted message
* @param string $key - encryption key
* @param int $block_size - pad the message by $block_size byte chunks to conceal encrypted data size. must match between encrypt/decrypt!
* @return string
* @see encrypt()
* @see https://github.com/jedisct1/libsodium/issues/392
*/
public static function decrypt($encrypted, $secret_key, $block_size = 1)
{
// unpack base64 message
$decoded = base64_decode($encrypted);

// check for general failures
if ($decoded === false) {
throw new
\Exception('The encoding failed');
}

// check for incomplete message. CRYPTO_SECRETBOX_MACBYTES doesn't seem to exist in this version...
if (!defined('CRYPTO_SECRETBOX_MACBYTES')) define('CRYPTO_SECRETBOX_MACBYTES', 16);
if (
mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + CRYPTO_SECRETBOX_MACBYTES)) {
throw new
\Exception('The message was truncated');
}

// pull nonce and ciphertext out of unpacked message
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');

// decrypt it and account for extra padding from $block_size (enforce 512 byte limit)
$decrypted_padded_message = sodium_crypto_secretbox_open($ciphertext, $nonce, $secret_key);
$message = sodium_unpad($decrypted_padded_message, $block_size <= 512 ? $block_size : 512);

// check for encrpytion failures
if ($message === false) {
throw new
\Exception('The message was tampered with in transit');
}

// cleanup
sodium_memzero($ciphertext);
sodium_memzero($secret_key);

return
$message;
}

}

?>
up
2
jedisct1 at php dot net
6 years ago
With sensitive data, use `sodium_bin2base64()` and `sodium_base642bin()` than `base64_encode()` and `base64_decode()`.

The sodium functions provide better protection against side channels, are more flexible, and perform stricter validation.
up
2
jedisct1 at php dot net
6 years ago
## Encrypt a single message using a secret key

Encryption:

```php
$secret_key = sodium_crypto_secretbox_keygen();
$message = 'Sensitive information';

$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$encrypted_message = sodium_crypto_secretbox($message, $nonce, $secret_key);
```

Decryption:

```php
$decrypted_message = sodium_crypto_secretbox_open($encrypted_message, $nonce, $secret_key);
```

How it works:

`$secret_key` is a secret key. Not a password. It's binary data, not
something designed to be human readable, but rather to have a key
space as large as possible for a given length.
The `keygen()` function creates such a key. That has to remain secret,
as it is used both to encrypt and decrypt data.

`$nonce` is a unique value. Like the secret, its length is fixed. But
it doesn't have to be secret, and can be sent along with the encrypted
message. The nonce doesn't have to be unpredicable either. It just has
to be unique for a given key. With the `secretbox()` API, using
`random_bytes()` is a totally fine way to generate nonces.

Encrypted messages are slightly larger than unencrypted messages,
because they include an authenticator, used by the decryption function
to check that the content was not altered.

## Encrypt a single message using a secret key, and hide its length

Encryption:

```php
$secret_key = sodium_crypto_secretbox_keygen();
$message = 'Sensitive information';
$block_size = 16;

$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$padded_message = sodium_pad($padded_message, $block_size);
$encrypted_message = sodium_crypto_secretbox($padded_message, $nonce, $secret_key);
```

Decryption:

```php
$decrypted_padded_message = sodium_crypto_secretbox_open($encrypted_message, $nonce, $secret_key);
$decrypted_message = sodium_unpad($decrypted_padded_message, $block_size);
```

How it works:

Sometimes, the length of a message may provide a lot of information
about its nature. If a message is one of "yes", "no" and "maybe",
encrypting the message doesn't help: knowing the length is enough to
know what the message is.

Padding is a technique to mitigate this, by making the length a
multiple of a given block size.

Messages must be padded prior to encryption, and unpadded after
decryption.
up
1
jedisct1 at php dot net
6 years ago
## Encrypt a file using a secret key

<?php
$secret_key
= sodium_crypto_secretstream_xchacha20poly1305_keygen();
$input_file = '/tmp/example.original';
$encrypted_file = '/tmp/example.enc';
$chunk_size = 4096;

$fd_in = fopen($input_file, 'rb');
$fd_out = fopen($encrypted_file, 'wb');

list(
$stream, $header) = sodium_crypto_secretstream_xchacha20poly1305_init_push($secret_key);

fwrite($fd_out, $header);

$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
do {
$chunk = fread($fd_in, $chunk_size);
if (
stream_get_meta_data($fd_in)['unread_bytes'] <= 0) {
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL;
}
$encrypted_chunk = sodium_crypto_secretstream_xchacha20poly1305_push($stream, $chunk, '', $tag);
fwrite($fd_out, $encrypted_chunk);
} while (
$tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);

fclose($fd_out);
fclose($fd_in);
?>

Decrypt the file:

<?php
$decrypted_file
= '/tmp/example.dec';

$fd_in = fopen($encrypted_file, 'rb');
$fd_out = fopen($decrypted_file, 'wb');

$header = fread($fd_in, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);

$stream = sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $secret_key);

$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
while (
stream_get_meta_data($fd_in)['unread_bytes'] > 0 &&
$tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL) {
$chunk = fread($fd_in, $chunk_size + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
list(
$decrypted_chunk, $tag) = sodium_crypto_secretstream_xchacha20poly1305_pull($stream, $chunk);
fwrite($fd_out, $decrypted_chunk);
}
$ok = stream_get_meta_data($fd_in)['unread_bytes'] <= 0;

fclose($fd_out);
fclose($fd_in);

if (!
$ok) {
die(
'Invalid/corrupted input');
}
?>

How it works:

There's a little bit more code than in the previous examples.

In fact, `crypto_secretbox()` would work to encrypt as file, but only
if that file is pretty small. Since we have to provide the entire
content as a string, it has to fit in memory.

If the file is large, we can split it into small chunks, and encrypt
chunks individually.

By doing do, we can encrypt arbitrary large files. But we need to make
sure that chunks cannot be deleted, truncated, duplicated and
reordered. In other words, we don't have a single "message", but a
stream of messages, and during the decryption process, we need a way
to check that the whole stream matches what we encrypted.

So we create a new stream (`init_push`) and push a sequence of messages
into it (`push`). Each individual message has a tag attached to it, by
default `TAG_MESSAGE`. In order for the decryption process to know
where the end of the stream is, we tag the last message with the
`TAG_FINAL` tag.

When we consume the stream (`init_pull`, then `pull` for each
message), we check that they can be properly decrypted, and retrieve
both the decrypted chunks and the attached tags. If we read the last
chunk (`TAG_FINAL`) and we are at the end of the file, we know that we
completely recovered the original stream.
up
2
jedisct1 at php dot net
6 years ago
You may want to use `feof($fd_in)` instead of `stream_get_meta_data($fd_in)['unread_bytes'] <= 0` in the examples above.

Or just read the documentation of the PHP extension here: https://github.com/jedisct1/libsodium-php -- it is always more up to date.
To Top