在http://en.wikipedia.org/wiki/Diffie-Hellman上面給出了這個金鑰交換協議的曆史,原理,重要文獻的連結,以及示範代碼。它的數學基礎就是離散對數這個數學難題。用它進行金鑰交換的過程簡述如下:
選取兩個大數p和g並公開,其中p是一個素數,g是p的一個模p本原單位根(primitive root module p),所謂本原單位根就是指在模p乘法運算下,g的1次方,2次方……(p-1)次方這p-1個數互不相同,並且取遍1到p-1;
對於Alice(其中的一個通訊者),隨機產生一個整數a,a對外保密,計算Ka = g^a mod p,將Ka發送給Bob;
對於Bob(另一個通訊者),隨機產生一個整數b,b對外保密,計算Kb = g^b mod p,將Kb發送給Alice;
在Alice方面,收到Bob送來的Kb後,計算出密鑰為:key = Kb^a mod p=(g^b)^a=g^(b*a) mod p;
對於Bob,收到Alice送來的Ka後,計算出密鑰為:key = Ka ^ b mod p=(g^a)^b=g^(a*b) mod p。
攻擊者知道p和g,並且截獲了Ka和Kb,但是當它們都是非常大的數的時候,依靠這四個數來計算a和b非常困難,這就是離散對數數學難題。
要實現Diffie-Hellman金鑰交換協議,需要能夠快速計算大數模冪,在模冪演算法中,仍需計算大數的乘法和模運算,所以整個過程需要三個演算法:高精度乘法,高精度除法(用來同時求出一個大數除以另一個大數的商和餘數),快速模冪演算法。
高精度的乘法和除法可以程式類比手算。快速模冪演算法也是從手算中總結出規律來,例如:
5^8 = (5^2)^4 = (25)^4 = (25^2)^2 = (625)^2,這樣,原來計算5^8需要做8次乘法,而現在則只需要三次乘法,分別是:5^2, 25^2, 625^2。這就是快速模冪演算法的基礎。將演算法描述出來,那就是:
演算法M:輸入整數a,b,p,計算a^b mod p:
M1.初始化c = 1
M2.如果b為0, 則c就是所要計算的結果。返回c的值。演算法結束。
M3.如果b為奇數,則令c = c *a mod p, 令b = b-1,轉到M2。
M4.如果b為偶數,則令a = a * a mod p, 令b = b / 2,轉到M2。
高精度試除法原理簡單,但是代碼實現起來需要仔細考慮一些細節。
我的示範代碼如下:
高精度運算類:
- class SuperNumber {
- public:
- SuperNumber() {
- memset(data, 0, MAX_SIZE);
- high = 0;
- }
- // 一般整型到SuperNumber的轉換,該版本中不支援負數
- SuperNumber(unsigned long l) {
- memset(data, 0, MAX_SIZE);
- high = 0;
- while(l) {
- data[++high] = l % 10;
- l /= 10;
- }
- }
- // str為字串形式表示的十進位數
- SuperNumber(const char* str) {
- assert(str != NULL);
- high = strlen(str);
- for(int i = high, j = 0; i >= 1; i--, j++) {
- data[i] = str[j] - '0';
- }
- }
- SuperNumber(const SuperNumber& s) {
- memcpy(data, s.data, MAX_SIZE);
- high = s.high;
- }
- operator const char*() const {
- return toString(10);
- }
- SuperNumber& operator=(const SuperNumber& s) {
- if(this != &s) {
- memcpy(data, s.data, MAX_SIZE);
- high = s.high;
- }
- return *this;
- }
- // 將資料置為0
- void reset() {
- memset(data, 0, MAX_SIZE);
- high = 0;
- }
- // str為字串形式表示的十進位數
- void setToStr(const char* str) {
- assert(str != NULL);
- high = strlen(str);
- for(int i = high, j = 0; i >= 1; i--, j++) {
- data[i] = str[j] - '0';
- }
- }
- // 將資料轉換成以base指定的進位的字串形式,預設為十進位
- const char* toString(int base = 10) const {
- static char buf[MAX_SIZE];
- const char table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- if(high == 0) return "0";
- assert(base >= 2); // 指定的進位應該不小於2
- // 進位轉換
- buf[MAX_SIZE-1] = '/0';
- int begin = MAX_SIZE-1;
- char temp[MAX_SIZE];
- memcpy(temp, data, MAX_SIZE);
- while(1) {
- // 找最高位的起始位置
- int h = high;
- while(temp[h] == 0 && h >= 1) h--;
- if(h == 0) break;
- // 除基取餘
- int t = 0;
- while(h >= 1) {
- t = t * 10 + temp[h];
- temp[h] = t / base;
- t = t % base;
- h--;
- }
- buf[--begin] = table[t];
- }
- return buf+begin;
- }
- // 乘法
- SuperNumber operator*(const SuperNumber& s) const {
- SuperNumber result; // default set to 0
- int i, j;
-
- // 相乘
- for(i = 1; i <= high; i++) {
- for(j = 1; j <= s.high; j++) {
- int k = data[i] * s.data[j] + result.data[i+j-1];
- result.data[i+j-1] = k % 10;
- result.data[i+j] += k / 10;
- }
- }
- // 進位
- for(i = 1; i < MAX_SIZE - 1; i++) {
- if(result.data[i] >= 10) {
- result.data[i+1] += result.data[i] / 10;
- result.data[i] %= 10;
- }
- }
- // 確定最高位
- for(i = MAX_SIZE-1; i >= 1 && result.data[i] == 0; i--);
- result.high = i;
- return result;
- }
- // 除法,內部調用doDivide來實現
- SuperNumber operator/(const SuperNumber& s) const {
- SuperNumber q, r;
- doDivide(s, q, r);
- return q;
- }
- // 模運算,內部調用doDivide來實現
- SuperNumber operator%(const SuperNumber& s) const {
- SuperNumber q, r;
- doDivide(s, q, r);
- return r;
- }
- // 除法運算,一次除法運算中同時得到商和餘數,運算子/和%的重載
- // 內部會調用這個函數,dest為除數,Q為商,R為餘數,演算法使用試除法
- void doDivide(const SuperNumber& dest, SuperNumber& Q, SuperNumber& R) const {
- int i, j, t;
- Q.reset();
- Q.high = high - dest.high + 1; // 商的初始位元
- R = *this; // 餘數初始實為被除數
- t = dest.high;
- // 判斷除法是否結束
- while(R >= dest) {
- // 迴圈相減進行試除
- while(dest >= R.sub(1, t)) {
- Q.data[Q.high--] = 0;
- ++t;
- }
- while(R.sub(1, t) >= dest) {
- // i為相減時被除數最低下標,j為除數最低下標
- for(i=R.high-t+1,j=1; j<=dest.high; i++,j++) {
- R.data[i] -= dest.data[j];
- if(R.data[i] < 0) {
- R.data[i] += 10;
- R.data[i+1] -= 1;
- }
- }
- while(R.data[i] < 0 && i <= R.high) {
- R.data[i] += 10;
- R.data[i+1] -= 1;
- ++i;
- }
- Q.data[Q.high] += 1;
- }
- // 一次試除結束,更新商的最高位下標
- Q.high -= 1;
- // 更新被除數的最高位下標
- while(R.data[R.high] == 0) {
- R.high--;
- t--;
- }
- t+=1; // 下一位被除數
- }
- Q.high = high - dest.high + 1;
- while(Q.data[Q.high] == 0) Q.high -= 1;
- R.high = high;
- while(R.data[R.high] == 0) R.high -= 1;
- }
- // 大數模冪演算法,很簡單的自然演算法,即將指數分解為二進位,換句
- // 更簡單的話來說,就是不斷地找平方模冪,而不是全部乘方後再
- // 做一次最終的模運算
- // a.power_mod(p, n)計算a^p mod n
- SuperNumber power_mod(int power, SuperNumber n) const {
- SuperNumber c("1"), t(*this);
- while(power) {
- if(power % 2) {
- c = c * t % n;
- power -= 1;
- } else {
- t = t * t % n;
- power /= 2;
- }
- }
- return c%n;
- }
- bool operator>=(const SuperNumber& s) const {
- if(high == s.high) {
- int k = high;
- while(data[k] == s.data[k] && k >= 1)k--;
- if(k < 1) return true; // equal
- return data[k] > s.data[k];
- } else if(high > s.high) return true;
- return false;
- }
- bool operator<(const SuperNumber& s) const {
- return !(*this >= s);
- }
- // 從十進位表示的最高位開始數起,數到第i位,從第i位開始截取連續
- // 的c位元字出來組成一個新的數。例如:資料是12345678925698,則
- // sub(3, 5)返回數字34567,如果數字不夠取,例如在34567上運行
- // sub(3, 5),因為34567從高位元起第3個數字是5,剩下的數字是567,
- // 最多隻有三個,不夠取要求的5個,這個時候返回567,不報錯。
- SuperNumber sub(int i, int c) const {
- SuperNumber ret;
- assert(high >= i); // 保證可截取
- i = high - i + 1; // 從高位元起的第i個數位的下標
- if(i >= c) {
- ret.high = c;
- while(c >= 1) ret.data[c--] = data[i--];
- } else {
- ret.high = i;
- while(i >= 1) {
- ret.data[i] = data[i];
- i--;
- }
- }
- // 過濾前置0
- while(ret.data[ret.high] == 0) ret.high--;
- return ret;
- }
- // I/O
- friend istream& operator>>(istream& in, SuperNumber& s) {
- char t[256];
- in >> t;
- s.setToStr(t);
- return in;
- }
- friend ostream& operator<<(ostream& out, const SuperNumber& s) {
- return out << s.toString(10);
- }
- private:
- enum {MAX_SIZE=256}; // 最大十進位位元
- // 須注意,使用data[0]儲存最高位所在下標是自己的一點小聰明,後來在
- // 調試的時候發現這是一個極大的錯誤,不過對於此題目來說可以應付
- char data[MAX_SIZE]; // 資料的內部表示,字串形式的十進位
- // 其中data[0]儲存最高位所在下標,data[1]
- // 儲存資料的最低位,也就是個位
- int high;
- };
主函數:
- int main(int argc, char **argv) {
- freopen("in.txt", "r", stdin);
- SuperNumberTest st;
- // st.run();
- // g和n都是超過2^127的素數。它們在DH演算法中公開
- SuperNumber g, n;
- int a, b;
- SuperNumber ka, kb, key;
- srand(time(0));
- cin >> g >> n;
- cout << "g = " << g << endl
- << "n = " << n << endl;
- cout << "/nThis is Alice:/n";
- a = rand();
- cout << "Alice get a random integer a = " << a << endl;
- cout << "Alice computer g^a mod n:/n";
- ka = g.power_mod(a, n);
- cout << "Alice compute out ka = " << ka << endl;
- cout << "/nThis is Bob:/n";
- b = rand();
- cout << "Bob get a random integer b = " << b << endl;
- cout << "Bob compute g^b mod n:/n";
- kb = g.power_mod(b, n);
- cout << "Bob compute out kb = " << kb << endl;
- cout << "/nAlice get kb from Bob, she compute out key is:/n";
- cout << kb.power_mod(a, n) << endl;
- cout << "/nBob get ka from Alice, he compute out key is:/n";
- cout << ka.power_mod(b, n) << endl;
- return 0;
- }
運行結果:
g = 170141183460469231731687303715884105757
n = 170141183460469231731687303715884106309
This is Alice:
Alice get a random integer a = 20276
Alice computer g^a mod n:
Alice compute out ka = 102075421398841759242347870420481896337
This is Bob:
Bob get a random integer b = 28664
Bob compute g^b mod n:
Bob compute out kb = 62348451302684698452476840835428450852
Alice get kb from Bob, she compute out key is:
80402514625208456390620786920929643017
Bob get ka from Alice, he compute out key is:
80402514625208456390620786920929643017
在http://oldpiewiki.yoonkn.com/cgi-bin/moin.cgi/DiffieHellmanKeyExchange 上面給出了使用GNU gmp庫實現的這個演算法的示範代碼