Key Deck
More of a thought experiment than anything else. Inspired by both the fact that netbooks and cloud storage make it somewhat plausible to replace your computing platform cheaply, and the prospect of having your computer pilfered, inspected or broken by immigration/security officials when traveling seems to be getting higher.
The solution? Keep your data online and encrypted, then when you get to your destination, buy a cheap netbook, and download all your stuff fresh. The only problem is transporting your secret key. Having it on an SD card in a camera might work okay, and you could even hide it steganographically in an image if you wanted. But if you don't want to carry anything technological with you, a standard deck of cards can store 225 bits in its ordering -- and if you've got an extra six cards in your deck (eg, a Knight card in each suit and two Jokers) you can handle 256 bits.
There's two possible ways to handle the conversion: you can either use a subset of the cards to specify your value (representing 0 by no cards, 1-52 as just 1 card, etc) or always use the entire deck (0 is the deck in its standard order, 1 is the first two cards swapped, etc). The former gives you the ability to represent a lot more numbers, but is less than an extra bit or so in reality, which makes it not terribly useful.
So here's the code:
#!/usr/bin/env python
# Copyright (c) 2009 Anthony Towns
# GPLv2
Deck setup
A standard 52 card deck, where "AS" is "ace of spades", "TH" is the "ten of hearts", etc:
deck52 = [b+a for a in "CDHS" for b in "A23456789TJQK"]
A 58 card deck, where "ND" is "knight of diamonds", and Jokers are represented by "XX" and "YY":
deck58 = [b+a for a in "CDHS" for b in "A23456789TJNQK"]+["XX","YY"]
You can, of course, use non-standard orders for these decks, for extra obscurity. The secrecy is intended to be encoded in the actual pack of cards, however.
Converting a number to an ordering
This is almost a base conversion function, using the cards as digits -- except that each time we use a card, our base reduces by one. Seen that way the code is fairly straightforward:
def num_to_cards(n,deck):
assert n >= 0
deck, base = deck[:], len(deck)
res = []
while deck != []:
n, digit, base = n // base, n % base, base - 1
res.append(deck.pop(digit))
assert n == 0
return res
Converting from an ordering to a number
We're essentially doing the same thing in reverse here. Note that we start with the least significant digit, though.
def cards_to_num(seq,deck):
n, mul, base = 0, 1, len(deck)
deck = deck[:]
for s in seq:
r = deck.index(s)
deck.pop(r)
n += r * mul
mul *= l
l -= 1
return n
And there you have it. An example of it in practice looks like:
if __name__ == "__main__":
o = num_to_cards(2**128-1, deck52)
print ", ".join(o)
# 9D, 5C, AC, TC, 6D, 8D, QD, 5S, 3D, TD, AH, 3C, 4S, 4C, 7D, KS,
# JH, 3H, KH, TH, 6H, 6C, 3S, 2S, 7C, 2C, 8C, 9C, JC, QC, KC, AS,
# 6S, 7S, 8S, 9S, TS, JS, QS, 2H, 4H, 5H, 7H, 8H, 9H, QH, AD, 2D,
# 4D, 5D, JD, KD
n = cards_to_num(o, deck52)
print "n = %d = 2**128-%d" % (n, 2**128-n)
# n = 340282366920938463463374607431768211455 = 2**128-1
Also note that the largest number representable by a deck is represented by reversing the deck, so:
big52 = cards_to_num(reversed(deck52), deck52)
print "big52 == %.2f%% of 2**226" % (big52 * 100.0 / 2**226)
# big52 == 74.79% of 2**226
big58 = cards_to_num(reversed(deck58), deck58)
print "big58 == %.2f%% of 2**261" % (big58 * 100.0 / 2**261)
# big58 == 63.44% of 2**261