Greetings. Today we are going to look at an implementation of tripcodes, a kind of hashing used for signing posts anonymously on the Internet.

There are different algorithms to do so, but one that we are interested in is one generating non-secure, old-fashioned tripcodes.

So what is it?

Say there is a website allowing to leave comments while staying anonymous. No registration, no login, no usernames.

You respond to a post and then a person is responding to your response. You start a conversation. You know that your posts are yours. But what about all the other users? Are you still talking to the same person or some bunch of kids around playing their tricks on you? No idea! To resolve that sort of confusion in some situations, a tripcode can be used.

The idea is simple: along with your post you can pass your wanted nickname and a password. The website takes a password and hashes it into a tripcode. On displaying posts, the tripcodes are attached to messages, so you can make sure this is the same person who knows the password. Of course, nobody demands people to claim authorship of their posts, but we are leaving that aside, as we are interested in an implementation.

Examples at hands

Implementing the algorithm takes a single subroutine. Yet we need a way to test our tripcodes. Let’s define some testing rules for your tripcode subroutine:

use Test; is tripcode('a'), '!ZnBI2EKkq.'; is tripcode('¥'), '!9xUxYS2dlM'; is tripcode('\\'), '!9xUxYS2dlM'; is tripcode('»'), '!cPUZU5OGFs'; is tripcode('?'), '!cPUZU5OGFs'; is tripcode('&'), '!MhCJJ7GVT.'; is tripcode('&'), '!QfHq1EEpoQ'; is tripcode('!@#heheh'), '!eW4OEFBKDU';

Raku code

Now let’s take a look at the algorithm:

Escape HTML characters

Convert all characters to CP932 encoding. For characters where it is not possible, use ? symbol

symbol Decode resulting bytes as UTF8

Generate salt for our hash. To do this, add H. string to our decoded string (as it might be empty!), take second and third characters. Next, substitute any “weird” characters (in ASCII terms, anything that has code below 46 ( . ) and above 122 ( z )) with a dot.

string to our decoded string (as it might be empty!), take second and third characters. Next, substitute any “weird” characters (in ASCII terms, anything that has code below 46 ( ) and above 122 ( )) with a dot. Translate some non-word characters ( :;<=>?@[\\]^_` ) into ABCDEFGabcdef .

) into . Use UNIX function crypt with the decoded string and a salt we got, and took last 10 characters of it.

with the decoded string and a salt we got, and took last 10 characters of it. That’s all!

There are quite a lot of steps, but let’s see how we can code such a task in Raku.

Let’s start with a sub declaration:

sub tripcode($ pass is copy ) { }

We are going to modify the $str variable in-place, so is copy trait of the parameter will help us against the “passed Str value is immutable” error.

Next, escape HTML:

sub tripcode($ pass is copy ) { $ pass .= trans ([ ' & ' , ' < ' , ' > ' ] => [ ' & ' , ' < ' , ' > ' ]); }

With the trans method, we can replace characters in a string using “left to right” correspondence, so & is replaced with & , < is replaced with < etc.

Next thing – dances with Windows 932.

$ pass .= trans ([ ' & ' , ' < ' , ' > ' ] => [ ' & ' , ' < ' , ' > ' ]); $ pass = ([ ~ ] $ pass . comb . map ({ ( try . encode( ' windows-932 ' )) // Buf . new ( 0x3F ) })) . decode;

Let’s imagine writing this line step by step:

# split $pass into single characters $ pass . comb # for every character in the list resultring from `comb` method call $ pass . comb . map ({ }) # try to encode it into the encoding we want $ pass . comb . map ({ try . encode( ' windows-932 ' ) }) # when `try` returned `Nil`, use `//` operator which means `If the left side is not defined,`use the right side $ pass . comb . map ({ (( try . encode( ' windows-932 ' )) // Buf . new ( 0x3F ) }) # Use [~] unary metaoperator, which is a shortcut for "join this array using this operator to join two single elements" ([ ~ ] $ pass . comb . map ({ ( try . encode( ' windows-932 ' )) // Buf . new ( 0x3F ) })) # At last, decode the resulting buffer and assign it to the variable $ pass = ([ ~ ] $ pass . comb . map ({ ( try . encode( ' windows-932 ' )) // Buf . new ( 0x3F ) })) . decode;

Now we need to generate some salt for our hash.

my $salt = " { $ pass } H. " . substr ( 1 , 2 ) . subst (/ < - [ . .. z ] > /, ' . ' ) . trans ( ' :;<=>?@[ \\ ]^_` ' => ' ABCDEFGabcdef ' );

Firstly, we add H. part to the password, then taking second and first characters using substr call. Note the second call is subst , which replaces anything outside of regex range with a dot. Here, substr is a short for substring , while subst is a short for substitute . Then goes our trans method.

As the next thing, we need to call UNIX crypt function. Luckily, we don’t need to implement it! In Raku’s ecosystem there is already a module Crypt::Libcrypt written by Jonathan Stove++. Let’s install it:

zef install Crypt::Libcrypt

Now we can import this module and have crypt subroutine at our service. The last line is simple:

' ! ' ~ crypt($ pass , $salt) . substr ( * -10 , 10 );

We don’t need to write an explicit return statement, as the last statement of a block is considered to be its return value. A call to crypt subroutine and our old friend substr with the first argument looking funny. The second argument is, as usual, a number of characters we want, while the first one is an expression with Whatever Star used. On call, the substr caller’s length is passed into this micro-block of code, so it is translated into 'foo'.substr('foo'.chars() - 10, 10) (but smarter inside).

Comprising everything, we get a full definition:

sub tripcode($ pass is copy ) { $ pass .= trans ([ ' & ' , ' < ' , ' > ' ] => [ ' & ' , ' < ' , ' > ' ]); $ pass = ([ ~ ] $ pass . comb . map ({ ( try . encode( ' windows-932 ' )) // Buf . new ( 0x3F ) })) . decode; my $salt = " { $ pass } H. " . substr ( 1 , 2 ) . subst (/ < - [ . .. z ] > /, ' . ' ) . trans ( ' :;<=>?@[ \\ ]^_` ' => ' ABCDEFGabcdef ' ); ' ! ' ~ crypt($ pass , $salt) . substr ( * -10 , 10 ); }

Check it:

> perl6 tripcode.p6 ok 1 - ok 2 - ok 3 - ok 4 - ok 5 - ok 6 - ok 7 - ok 8 -

A success, all the checks we prepared pass! As we successfully implemented the algorithm using only four lines of code, it is time to refill some hot drink. Have a nice day!