Search code examples
phplibsodium

LibSodium functions return unreadable characters


I am following along with a tutorial on encryption: https://php.watch/articles/modern-php-encryption-decryption-sodium. In working with the Sodium extension I'm just baffled by a few things. Googling is returning frustratingly little help. (Most of the results are just duplications of the php.net/manual.)

1. In various articles I'm reading, the result of sodium_crypto_*_encrypt() is something familiar:

// ex. DEx9ATXEg/eRq8GWD3NT5BatB3m31WED

Whenever I echo it out myself I get something like:

// ex. 𫦢�2(*���3�CV��Wu��R~�u���H��

which I'm certain won't store correctly on a database. Nowhere in the articles or documentation does it mention anything about charset weirdness. I can throw a header('Content-Type: text/html; charset=ISO-8859-1') in there, but I still get weird characters I'm not certain are right since I'm not finding any threads talking about this:

// ex. ÑAÁ5eŠ…n@±'ýÞÃ1è9ÜÈ̳¬"CžãÚ0ÿÛ

2. I can't find any information about the best practice for storing keys or nonces.

I just figured this obvious-to-security-folks-but-not-to-others bit of information would be a regularly discussed part of articles on keygens and nonces and such. Seeing as both my keygen and nonce functions (at least in the Sodium library) seem to return non-UTF-8 gibberish, what do I do with it? fwrite it out to a file to be referenced later? Pass it directly to my database? Copy/pasting certainly doesn't work right with it being wingdings.

Other than these things, everything else in the encryption/decryption process makes complete sense to me. I'm far from new to PHP development, I just can't figure this out.


Solution

  • Came across https://stackoverflow.com/a/44874239/1128978 answering "PHP random_bytes returns unreadable characters"

    random_bytes generates an arbitrary length string of cryptographic random bytes...

    And suggests to use bin2hex to get readable characters. So amending my usages:

    // Generate $ciphertext
    $message = 'This is a secret message';
    $key = sodium_crypto_*_keygen();
    $nonce = random_bytes(SODIUM_CRYPTO_*BYTES);
    $ciphertext = sodium_crypto_*_encrypt($message, '', $nonce, $key);
    
    // Store hexadecimal versions of binary output
    $nonce_hex = bin2hex($nonce);
    $key_hex = bin2hex($key);
    $ciphertext_hex = bin2hex($ciphertext);
    
    // When ready to decrypt, convert hexadecimal values back to binary
    $ciphertext_bin = hex2bin($ciphertext_hex);
    $nonce_bin = hex2bin($nonce_hex);
    $key_bin = hex2bin($key_hex);
    
    $decrypted = sodium_crypto_*_decrypt($ciphertext_bin, '', $nonce_bin, $key_bin);
    // "This is a secret message"
    

    So making lots of use of bin2hex and hex2bin, but this now makes sense. Effectively solved, though not confident this is the proper way to work with it. I still have no idea why this isn't pointed out anywhere in php.net/manual nor in any of the articles/comments I've been perusing.