C strings are simple: they start at a memory address and end at the first zero/null byte (\0). But what if your data contains nulls? If you use standard string types like String or Array[Char], C (and Affix) will assume the data ends at the first null byte. This is fatal for encryption, compression, and binary formats.

To handle this, we must stop thinking in characters and start thinking in bytes.

The Recipe

This is a demonstration of the XOR cipher. Since the data might contain nulls, we handle it as a raw buffer.

use v5.40;
use Affix qw[:all];
use Affix::Build;

# Compile the C library
my $c = Affix::Build->new();
$c->add( \<<~'END', lang => 'c' );
    #include <stdlib.h>
    #include <string.h>

    char * string_crypt(const char * input, int len, const char * key) {
        char * output = malloc(len + 1);
        if (!output) return NULL;
        output[len] = '\0';

        int key_len = strlen(key);
        for (int i = 0; i < len; i++) {
            output[i] = input[i] ^ key[i % key_len];
        }
        return output;
    }

    void string_free(void * p) { free(p); }
END
my $lib = $c->link;

# Bind the functions
# Return Pointer[Void] to get the raw address (Pin) without interpretation.
affix $lib, 'string_crypt', [ String, Int, String ] => Pointer [Void];
affix $lib, 'string_free',  [ Pointer [Void] ]      => Void;

# The Wrapper
sub cipher( $input, $key ) {
    my $len = length($input);

    # Call C. We get back a Pin (unmanaged pointer).
    my $ptr = string_crypt( $input, $len, $key );
    return undef if is_null($ptr);

    # Create a view of the memory as an Array of Bytes (UInt8).
    # Important:
    #   Array[Char]  => Reads up to NULL (String behavior)
    #   Array[UInt8] => Reads exactly $len bytes (Binary behavior)
    my $binary_string = cast( $ptr, Array [ UInt8, $len ] );

    # Clean up the C memory immediately
    string_free($ptr);
    return $binary_string;
}

# Run it
my $orig = 'hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!';
my $key  = 'foobar';
my $enc  = cipher( $orig, $key );
my $dec  = cipher( $enc,  $key );
say "Original:  $orig";
say "Decrypted: $dec";

# Verify the binary data size
say "Encrypted length: " . length($enc);
use JSON::PP;
say "Encrypted blob:   " . encode_json( [$enc] );

How It Works

Kitchen Reminders