1 #include <botan/botan.h>
2 #include <botan/fpe_fe1.h>
3 #include <botan/sha160.h>
4
5 using namespace Botan;
6
7 #include <iostream>
8 #include <stdexcept>
9
10 namespace {
11
luhn_checksum(u64bit cc_number)12 byte luhn_checksum(u64bit cc_number)
13 {
14 byte sum = 0;
15
16 bool alt = false;
17 while(cc_number)
18 {
19 byte digit = cc_number % 10;
20 if(alt)
21 {
22 digit *= 2;
23 if(digit > 9)
24 digit -= 9;
25 }
26
27 sum += digit;
28
29 cc_number /= 10;
30 alt = !alt;
31 }
32
33 return (sum % 10);
34 }
35
luhn_check(u64bit cc_number)36 bool luhn_check(u64bit cc_number)
37 {
38 return (luhn_checksum(cc_number) == 0);
39 }
40
cc_rank(u64bit cc_number)41 u64bit cc_rank(u64bit cc_number)
42 {
43 // Remove Luhn checksum
44 return cc_number / 10;
45 }
46
cc_derank(u64bit cc_number)47 u64bit cc_derank(u64bit cc_number)
48 {
49 for(u32bit i = 0; i != 10; ++i)
50 if(luhn_check(cc_number * 10 + i))
51 return (cc_number * 10 + i);
52 return 0;
53 }
54
55 /*
56 * Use the SHA-1 hash of the account name or ID as a tweak
57 */
sha1(const std::string & acct_name)58 SecureVector<byte> sha1(const std::string& acct_name)
59 {
60 SHA_160 hash;
61 hash.update(acct_name);
62 return hash.final();
63 }
64
encrypt_cc_number(u64bit cc_number,const SymmetricKey & key,const std::string & acct_name)65 u64bit encrypt_cc_number(u64bit cc_number,
66 const SymmetricKey& key,
67 const std::string& acct_name)
68 {
69 BigInt n = 1000000000000000;
70
71 u64bit cc_ranked = cc_rank(cc_number);
72
73 BigInt c = FPE::fe1_encrypt(n, cc_ranked, key, sha1(acct_name));
74
75 if(c.bits() > 50)
76 throw std::runtime_error("FPE produced a number too large");
77
78 u64bit enc_cc = 0;
79 for(u32bit i = 0; i != 7; ++i)
80 enc_cc = (enc_cc << 8) | c.byte_at(6-i);
81 return cc_derank(enc_cc);
82 }
83
decrypt_cc_number(u64bit enc_cc,const SymmetricKey & key,const std::string & acct_name)84 u64bit decrypt_cc_number(u64bit enc_cc,
85 const SymmetricKey& key,
86 const std::string& acct_name)
87 {
88 BigInt n = 1000000000000000;
89
90 u64bit cc_ranked = cc_rank(enc_cc);
91
92 BigInt c = FPE::fe1_decrypt(n, cc_ranked, key, sha1(acct_name));
93
94 if(c.bits() > 50)
95 throw std::runtime_error("FPE produced a number too large");
96
97 u64bit dec_cc = 0;
98 for(u32bit i = 0; i != 7; ++i)
99 dec_cc = (dec_cc << 8) | c.byte_at(6-i);
100 return cc_derank(dec_cc);
101 }
102
103 }
104
main(int argc,char * argv[])105 int main(int argc, char* argv[])
106 {
107 LibraryInitializer init;
108
109 if(argc != 4)
110 {
111 std::cout << "Usage: " << argv[0] << " cc-number acct-name passwd\n";
112 return 1;
113 }
114
115 u64bit cc_number = atoll(argv[1]);
116 std::string acct_name = argv[2];
117 std::string passwd = argv[3];
118
119 std::cout << "Input was: " << cc_number << ' '
120 << luhn_check(cc_number) << '\n';
121
122 /*
123 * In practice something like PBKDF2 with a salt and high iteration
124 * count would be a good idea.
125 */
126 SymmetricKey key = sha1(passwd);
127
128 u64bit enc_cc = encrypt_cc_number(cc_number, key, acct_name);
129
130 std::cout << "Encrypted: " << enc_cc
131 << ' ' << luhn_check(enc_cc) << '\n';
132
133 u64bit dec_cc = decrypt_cc_number(enc_cc, key, acct_name);
134
135 std::cout << "Decrypted: " << dec_cc
136 << ' ' << luhn_check(dec_cc) << '\n';
137
138 if(dec_cc != cc_number)
139 std::cout << "Something went wrong :( Bad CC checksum?\n";
140 }
141