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 - 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. - Translate some non-word characters (
:;<=>?@[\\]^_`
) intoABCDEFGabcdef
. - Use UNIX function
crypt
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!
Very helpful. When experienced users chain complex routines together documentation is often nil. When you break the chain into a step-by-step explanation it really helps me understand the sequence better, thanks!
LikeLike
Is the HTML-escaping missing something? – Replacing “&” with “&” doesn’t appear to achieve anything.
LikeLike
Sadly, the wordpress editor has murdered some escaping in the code. My sincere apologies for the inconveniences, now the post is fixed.
LikeLike