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