<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Rsa on chai2010 的博客</title>
    <link>https://chai2010.cn/tags/rsa/</link>
    <description>Recent content in Rsa on chai2010 的博客</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Wed, 27 Dec 2006 00:00:00 +0000</lastBuildDate>
    
        <atom:link href="https://chai2010.cn/tags/rsa/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>RSA学习笔记</title>
      <link>https://chai2010.cn/post/2006/rsa-notes/</link>
      <pubDate>Wed, 27 Dec 2006 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/2006/rsa-notes/</guid>
      
        <description>&lt;p&gt;先贴学习RSA时的练习代码(uint32范围):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// RSA加密系统:
// 1. 随机选取2个大的素数p和q
// 2. 根据n=p*q式计算出n的值
// 3. 选取一个与f(n)互素的小奇数e. 其中f(n)为欧拉装载函数, 值为(p-1)(q-1)
// 4. 对于模f(n), 计算e的乘法逆元d
// 5. 公开对P=(e,n), 并把它作为RSA公开秘钥
// 6. 把对S=(d,n)进行保密, 并把它作为RSA的机密秘钥

// 加密过程: C = (M^e)%n
// 解秘过程: M = (C^d)%n

#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;time.h&amp;gt;

// 返回一个位于low和high之间的随机数
#define ran(low,high) ((rand()%((high)-(low)+1))+(low))

// 计算数组的元素个数
#define NELEMS(x) ((sizeof(x)) / (sizeof((x)[0])))

// 构造素数序列primes[]
void makePrimes(int primes[], int num)
{
    primes[0] = 2;

    for(int p = 3, cnt = 1; cnt &amp;lt; num; p += 2)
    {
        for(int k = 0; ; ++k)
        {
            // 可以在div时得到余数
            div_t dt = div(p, primes[k]);

            if(dt.rem == 0) break;
            if(dt.quot &amp;lt;= primes[k]) { primes[cnt++] = p; break; }
        }
    }
}

// 返回2个随机素数, 在10000~40000之间
void rand_pq(int&amp;amp; p, int&amp;amp; q)
{
    // [2, 2^16]范围内有6542个素数
    static int primes[1024*8];
    static int low, high;

    // 初始化(只处理一次)
    if(low == 0 &amp;amp;&amp;amp; high == 0)
    {
        // 生成素数序列
        makePrimes(primes, NELEMS(primes));

        // 计算10000和40000附近的素数下标
        for(int i = 0; i &amp;lt; NELEMS(primes); ++i)
        {
            if(!low &amp;amp;&amp;amp; primes[i] &amp;gt; 10000) { low = i; continue; }
            if(!high &amp;amp;&amp;amp; primes[i] &amp;gt; 40000) { high = i; break; }
        }

        // 设置随机数种子
        srand(time(NULL));
    }

    // 在low, high之间返回2个不同的随机数
    int r1 = ran(low,high);
    int r2 = ran(low,high);

    // r1和r2不能相等
    while(r2 == r1) r2 = ran(low,high);

    // 返回r1,r2位置对应的素数
    p = primes[r1];
    q = primes[r2];
}

// 扩展的欧几里得算法
int xgcd(int a, int b, int&amp;amp; x, int&amp;amp; y)
{
    if(b == 0) { x = 1, y = 0; return a; }

    int d = xgcd(b, a%b, x, y);
    int t = x-(a/b)*y; x = y; y = t;

    return d;
}

// 针对(p-1)(q-1)产生e和d
void rand_ed(int p, int q, int&amp;amp; e, int &amp;amp;d)
{
    int n = (p-1)*(q-1);
    int x, y;

    // 选择一个小的互素奇数e
    for(e = 37; ; e += 2)
    {
        if(xgcd(e, n, x, y) == 1) break;
    }

    // 将d转换到[0, n-1]之间
    while(x &amp;gt; 0) x -= n;
    while(x &amp;lt; 0) x += n;

    d = x;
}

// 计算(u*v)%m
unsigned mul_mod(unsigned u, unsigned v, unsigned z)
{
    // 如果u*v没有溢出, 则直接计算
    if((u*v)/u == v) return (u*v)%z;

    // 进行长乘法(结果为64位)
    unsigned u0, v0, w0;
    unsigned u1, v1, w1, w2, t;

    u0 = u &amp;amp; 0xFFFF;  u1 = u &amp;gt;&amp;gt; 16;
    v0 = v &amp;amp; 0xFFFF;  v1 = v &amp;gt;&amp;gt; 16;
    w0 = u0*v0;
    t  = u1*v0 + (w0 &amp;gt;&amp;gt; 16);
    w1 = t &amp;amp; 0xFFFF;
    w2 = t &amp;gt;&amp;gt; 16;
    w1 = u0*v1 + w1;

    // x为高32位, y为低32位
    unsigned x = u1*v1 + w2 + (w1 &amp;gt;&amp;gt; 16);
    unsigned y = u*v;

    // 进行长除法(被除数为64位)
    for (int i = 1; i &amp;lt;= 32; i++)
    {
        t = (int)x &amp;gt;&amp;gt; 31;           // All 1&#39;s if x(31) = 1.

        x = (x &amp;lt;&amp;lt; 1) | (y &amp;gt;&amp;gt; 31);   // Shift x || y left
        y &amp;lt;&amp;lt;= 1;                    // one bit.

        if((x|t) &amp;gt;= z) { x -= z; y++; }
    }

    return x; // y为商, x为余数
}

// 计算(a^p)%n
unsigned pow_mod(unsigned a, unsigned p, unsigned n)
{
    unsigned k = 1;

    // 反复平方法
    while(p &amp;gt; 1)
    {
        if(p&amp;amp;1) k = mul_mod(k, a, n);
        a = mul_mod(a, a, n); p &amp;gt;&amp;gt;= 1;
    }

    return mul_mod(k, a, n);
}

// 产生RSA需要的所有参数
void rsa_rand(int&amp;amp; e, int&amp;amp; d, int&amp;amp; n)
{
    int p, q;

    rand_pq(p, q);
    rand_ed(p, q, e, d);
    n = p*q;
}

// 加密和解密的过程
int rsa_encryp(int m, int ed, int n)
{
    assert(m &amp;gt; 0 &amp;amp;&amp;amp; m &amp;lt; n);
    return pow_mod(m, ed, n);
}

// RSA加密测试
int main(void)
{
    int e, d, n;
    int m = 119; // 需要加密的数据

    printf(&amp;quot;RSA加密系统.\n\n&amp;quot;);

    // 产生秘钥
    rsa_rand(e, d, n);
    printf(&amp;quot;秘钥: e = %d, d = %d, n = %d\n\n&amp;quot;, e, d, n);

    while(1)
    {
        printf(&amp;quot;\n输入数据M(0&amp;lt;M&amp;lt;n): &amp;quot;);
        if(scanf(&amp;quot;%d&amp;quot;, &amp;amp;m) != 1) break;
        if(m &amp;lt;= 0 || m &amp;gt;= n) break;

        // 加密前数据
        printf(&amp;quot;加密前: %d\n&amp;quot;, m);

        // 加密文件, 秘钥为(e, n)
        printf(&amp;quot;加密后: %d\n&amp;quot;, m = rsa_encryp(m, e, n));

        // 解密文件, 秘钥为(d, n)
        printf(&amp;quot;解密后: %d\n&amp;quot;, m = rsa_encryp(m, d, n));
    }

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;关于学习RSA的几点体会:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 该实现的RSA密码强度很低, 可能仅满足于学习需要&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;具体地n = p*q, n为32位int类型数据, 则p/q中必然
有一个值小于等于sqrt(n) &amp;lt;= sqrt(2^32) == 2^16.
而[0,2^16]范围内只有6542个素数, 因此因式分解n的
难度很低.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 实际产生大的随机素数时, 一般采用费马小定理&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;费马定理只是一种概率测试. 由于这里涉及的素数范围
很小, 因此直接从素数表中随机选取. 素数表的构造过
程很有意思, makePrimes从第一个素数2开始, 根据已知
的素数逐步扩充未知的素数.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. 关于GCD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这里采用了扩展的GCD算法, 为的是得到下面的等式:
x*a + y*b == gcd(a,b).
根据x可以得到模线形方程a*x=1(mod b)的解.
GCD还有一种二进制的实现, 这里采用的是Euclid给的
算法.&lt;/p&gt;

&lt;p&gt;观察代码可以发现, a/b和x/y参数的传递方向是相反:
a/b在进栈的时候向栈内传递信息; x/y则是在出栈的
时候从栈底向外传递信息.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. 关于与(p-1)*(q-1)互素的小奇数e&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;满足e的条件的基数很多, 但是具体取哪个呢? 最小的
是否可以?&lt;/p&gt;

&lt;p&gt;个人觉得e的值最好不要小于log2(n). 因为如果e的值
太小, 比如为3, 那么当m^3也小于n时就相当于没有加密
了. 针对这里n为int型数据的话, e大于32应该就可以了.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. m为1和n-1时&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;m为1的话, 加密解密都是1, 因为1的x次幂依然为1.
m为n-1, 测试了一些数据, 加密后的值依然为n-1,
我想应该可以从数论角度给出证明, 数学比较强的朋友
可以尝试证明一下.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. m &amp;lt; 0 or m &amp;gt;= n&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;因为RSA需要用模n运算, 因此如果m超出n范围的话, 解密
肯定会出现错误. 最好回避.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. m为0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个情况也比较特殊, 回避.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. (a^p)%n运算&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;由于a^p结果可能溢出, 因此采用了长乘法(乘积为64位数).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. 函数f(a) = (a^p)%n的循环周期&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;由于函数f的值域固定(0,n-1), 因此这个函数必然有循环周期.
大家有感兴趣可以自己测试一下循环周期.&lt;/p&gt;
</description>
      
    </item>
    
  </channel>
</rss>