Introduction

This is a technical document outlining the methods used to encode and secure messages using a standard pack of playing cards.

Messages are encoded and decoded using JavaScript in the web browser.  Neither passwords nor messages are transmitted from the computer on which the page is loaded.  Use of the page is not logged by the author, although the company hosting the web site may record visitor information.  Encoding messages in a pack of playing cards provides plausible deniability, provided that you always use a password and use your web browser's incognito / private browsing mode to avoid having these web pages in your browsing history.  Note that hiding the introduction creates a cookie.

Every message that is encoded is checked against the decoder, so you can be confident that the recipient will be able to decode the message from the cards that are displayed.

Card images are pre-loaded when the page loads, but it is still possible for there to be a delay when the cards are re-displayed as you type.  To ensure that only an up-to-date selection is used, the cards are hidden and a pink background is shown whenever an image is being reloaded.  Therefore, it should be apparent to you when the correct sequence has been fully displayed.

Password processing

Passwords are salted and key strengthening is applied using the SHA-256 algorithm.  Rather than attempting to apply cryptography to the message itself, hashes based on the password are simply used to re-order the cards before assigning numbers to them.

SHA-256 has about 1,435 million times more combinations than a pack of playing cards, so every password should correspond to a unique shuffle of the pack.

Short messages could conceivably expose information about the password, as unused cards would be essentially the same for each short message.  To counter this, the cards are re-arranged after each card that is selected when encoding the message.

Encoding process

See: Large integer library (JavaScript) – simple alternative to BigInt

Each message is converted into a single, large integer by successively multiplying by 256 and then adding each byte value.  Encoding part of the result with the first playing card simply involves dividing this number by 52 (or 104 if using both sides) and taking the remainder.  The left over part of the number is divided by the consecutively smaller number of available cards – e.g. from 51 down to 1 – until either a zero result is reached or all cards have been used.

When the checkbox is ticked to indicate that all cards should be used, any left over cards are used to encode zeros.

Encoding using one side

When all cards are required to be the same way up, as pack of playing cards normally would be, the number of combinations is equivalent to 225 bits of information.

This corresponds to 28 8-bit bytes with one spare bit.

Encoding using both sides

When cards are allowed to be either face up or face down, to provide 52 bits of extra information, the number of combinations is equivalent to 277 bits of information.

This corresponds to 34 8-bit bytes with 5 spare bits.

There are 3 possible encoding methods, indicated by the third/fourth bits described above.

Unicode characters that fall outside of the Basic Multilingual Plane (BMP), e.g. the U+1D306 tetragram for centre (𝌆), are supported indirectly.  The web browser typically treats them as a pair of 2-byte Unicode characters (a surrogate pair) and the encoder/decoder handles them the same as any pair of Unicode characters.

Even though the encoding gives a different result when the user indicates that both sides are to be used, it is possible – especially for short messages – that the selection given by the encoder will result in all cards being face up by chance.  The second bit, described above, ensures that the decoder knows which scheme was used.

Pair compression

See: Pair compression library (JavaScript) – alternative to Smaz

By default, the pair compression algorithm expects "printable" ASCII characters (byte codes 32 to 126) to make up most of the message, which works very well for short messages in English (better than Smaz for most regular text) and with some success for other Western European languages.  If not compressed, normal ASCII values are used.

159 codes are used to represent common pairs of English characters, using byte values from 0-31 and 128-254 - for example, 177 is used to represent the common pair of letters "th".

A single non-ASCII character is escaped using a byte value of 127 followed by the Unicode value as 2 bytes.  Multiple non-ASCII characters are escaped with a byte value of 255, followed a byte representing the number of characters (the count minus one) and then each of the 2-byte Unicode characters.  Less common ASCII control codes – for tabs, line feeds, etc. – are treated as Unicode byte pairs.

The dictionary of pairs was created by checking the frequencies of character pairs found in sample text from a selection of short stories and a set of SMS text messages.  The pair frequencies were ranked from each source and the ranks combined to find those common to both types of text.