IOS應用安全-加解密算法簡述

IOS應用安全-加解密算法簡述

導讀
客戶端經(jīng)常遇到需要對數(shù)據(jù)進行加密的情況佩谣,那應該如何加密第步,選用什么樣的加密算法鸳玩,是本文想要討論的問題。

如果把我們的數(shù)據(jù)比作筆記,那數(shù)據(jù)加密相當于給筆記本上了鎖褒颈,解密相當于打開鎖看到筆記柒巫。而打開鎖的鑰匙一定是在私人手里的,外人是打不開的谷丸。所以數(shù)據(jù)加密一定有三個關鍵字:

1.加密
2.解密
3.秘鑰

所以有些常見的算法不是數(shù)據(jù)加密的范圍堡掏,這個開發(fā)需要注意。比如Base64編碼刨疼,MD5算法泉唁。

Base64只是把數(shù)據(jù)編碼,通俗講只是把原來用漢語寫的筆記內(nèi)容揩慕,改成用英語寫的內(nèi)容亭畜,只要懂轉換規(guī)則的任何人都能得到數(shù)據(jù)。所以老板說把數(shù)據(jù)加下密迎卤,一定不是讓你Base64一下或者用其他編碼重新編碼下拴鸵,編碼算法不涉及到數(shù)據(jù)安全。

MD5算法也是數(shù)據(jù)處理的一種方式蜗搔,更多的被用在數(shù)據(jù)驗證身上劲藐。用上面的例子來講,MD5算法把整本書的內(nèi)容變成了一句標題樟凄,通過標題是沒辦法推算出整個書講什么的聘芜。因為根本沒有解密的步驟,所以也不屬于加密算法不同。

字符編碼

計算機的所有數(shù)據(jù)厉膀,最終都是由多個二進制bit(0/1)來存儲和傳輸?shù)模窃趺磸?/1轉化成我們可讀的文字二拐,就涉及到編碼的知識了。下面是基礎的編碼概念凳兵。

ASCII (NSASCIIStringEncoding)

使用一個字節(jié)大小表示的128個字符百新。其中這些字符主要是英文字符,現(xiàn)在很少使用這個編碼庐扫,因為不夠用饭望。ASCII字符占用一個字節(jié)ASCII碼表

主要使用到的是英文字母的大小寫轉換形庭。大寫的A~Z編碼+32等于小寫的a~z铅辞。

UNICODE (NSUnicodeStringEncoding)

ASCII只能表示128個字符,對于英文國家來說足夠了萨醒,對于我們中國來說斟珊,我們有幾萬個漢字不夠啊。于是我們創(chuàng)造出了GB2312等等我們自己的字符集富纸。日本也覺得我也不夠啊囤踩,我也搞個字符集旨椒。這些字符集彼此是不兼容的,沒辦法轉換堵漱,同樣的字符ABCD,我們可能表示,日本就可能就表示综慎。于是程序猿們覺得我要搞個標準,大家都按照標準來勤庐。

于是就有了UNICODE編碼示惊。它是所有字符的國際標準編碼字符集。這個是為了解決ASCII字符不夠的問題愉镰。同時讓所有組織使用同一套編碼規(guī)則涝涤,解決編碼不兼容的問題。所以現(xiàn)在通用的編碼規(guī)則都是UNICODE編碼岛杀。UNICODE向下兼容ASCII編碼阔拳。UNICODE最大長度可以到4個字節(jié)。不過通常只使用兩個字節(jié)表示类嗤。所以通常認為UNICODE占用2字節(jié)數(shù)據(jù)糊肠。

UTF-8 (NSUTF8StringEncoding)

其實UNICODE已經(jīng)足夠使用了,不過因為如果是ASCII表示的字符(比如英文)只需要1字節(jié)就可以了遗锣,UNICODE表示的話其中一個字節(jié)全是0货裹,這個字節(jié)浪費了,英語國家的程序猿覺得:我靠精偿,我又不需要那么多復雜的字符,浪費我流量和空間啊弧圆,不行!笔咽!搔预,于是出現(xiàn)了對UNICODE的轉換,也就是UTF-8格式叶组,可以保證原ASCII字符依然用一個字節(jié)表示拯田,非ASCII字符使用多個字符表示。

UNICODE到UTF-8的規(guī)則如下:

  1. 按照UNICODE編碼的范圍甩十,算出需要幾個字節(jié)船庇,比如1個字節(jié)數(shù),2個字數(shù)節(jié)侣监,3個字節(jié)數(shù)鸭轮,4個字節(jié)數(shù)。具體范圍參考下面的圖橄霉。
  2. 單字節(jié)和ASCII碼完全相同窃爷,
  3. 對于其他字節(jié)數(shù),字節(jié)1的前面用1填充,幾個字節(jié)數(shù)就添加幾個1吞鸭,后面補一個0寺董。其他字節(jié)都用10開頭。
  4. 剩余的位置刻剥,按照順序把原始數(shù)據(jù)補齊遮咖。
utf_8.png

例子:

“漢”字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間造虏,使用用3字節(jié)模板了:1110xxxx 10xxxxxx 10xxxxxx御吞。將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x漓藕,得到:11100110 10110001 10001001陶珠,即E6 B1 89。

對于UTF-8編碼的文件享钞,會在文件頭寫入EF BB BF,表明是UTF-8編碼揍诽。

UTF-16 (NSUTF16StringEncoding)

UTF-16的編碼方法是:

  • 如果二進制(b字符編碼小于0x10000,也就是十進制的0到65535之內(nèi)栗竖,則直接使用兩字節(jié)表示暑脆。
  • 如果二進制b字符編碼大于等于0x10000,將b-0x10000的結果中的前 10 位作為高位和0xD800進行邏輯或操作狐肢,將后10 bit作為低位和0xDC00做邏輯或操作添吗,這樣組成的4個字節(jié)就構成了對應的編碼。

舉個例子份名。假設要算(U+2A6A5碟联,對應繁體字龍)在UTF-16下的值,因為它超過 U+FFFF僵腺,所以 2A6A5-10000=0x1A6A5鲤孵。

前10位0001 1010 01 | 0xD800 = 0xD896。

后10位10 1010 0101 | 0xDC00 = 0xDEA5想邦。

所以U+ 2A6A5 在UTF-16中的編碼是D8 96 DE A5裤纹。

注:上文參考:精確解釋Unicode

在IOS程序里面NSUTF16StringEncoding和NSUnicodeStringEncoding是等價的。

UTF-16大端/小端(NSUTF16BigEndianStringEncoding/NSUTF16LittleEndianStringEncoding)

大小端主要表明了丧没,系統(tǒng)存儲數(shù)據(jù)的順序。因為UTF-16至少兩個字節(jié)锡移,這兩個字節(jié)傳輸過來后呕童,接收的人需要知道哪個字節(jié)是在前,哪個字節(jié)在后淆珊。然后系統(tǒng)才知道改如何存取夺饲。

Unicode規(guī)范中用字節(jié)序標記字符(BOM)來標識字節(jié)序,它的編碼是FEFF。這樣如果接收者收到FEFF往声,就表明這個字節(jié)流是高位在前的擂找;如果收到FFFE,就表明這個字節(jié)流是低位在前的浩销。

比如“漢”字的Unicode編碼是0x6C49贯涎。

對于大端的文件數(shù)據(jù)為:FE FF 6c 49
對于小端的文件數(shù)據(jù)為:FF FE 49 6c

對于大小端的概念,本人經(jīng)常搞混慢洋,什么高地址存低字節(jié)的塘雳,繞一繞就暈了。下面是我的理解:

  1. 對于一個16進制數(shù)0x1234,我們知道這個數(shù)對應的是兩個字節(jié)普筹,占用16個比特败明。
  2. 系統(tǒng)中是按照字節(jié)為單位去保存數(shù)據(jù)的。一個地址空間對應1個字節(jié)太防。比如0x1234如果要存儲在計算機里妻顶,需要占用兩個地址空間。我們假設這個地址空間起始是0x00蜒车,因為需要兩個字節(jié)讳嘱,所以還需要一個地址空間來保存,即0x01醇王。其中明顯0x01是高地址空間呢燥。
  3. 所以問題就在于,對于0x1234這個數(shù)據(jù)保存寓娩,是0x01地址保存0x12還是保存0x34叛氨。
  4. 如果把0x1234看成字符串形式,按照正常順序存儲棘伴,先存0x12,后存0x34,對應的就是大端模式寞埠。
  5. 如果按照字節(jié)順序,0x12是高位焊夸,0x34是低位仁连,應該0x12存儲在高位地址0x02,低位字節(jié)0x34存儲在低位地址0x01。這種方式就是小端模式阱穗。
  6. 為了怕記混饭冬,可以這么記:我最大,按字符串順序存儲揪阶,我看的最舒服所以是大端昌抠。反面的就是小端的。
地址偏移 大端模式 小端模式
0x00 12 34
0x01 34 12

附:代碼判斷大小端的代碼鲁僚。

原理是生成一個兩字節(jié)的數(shù)據(jù)炊苫,然后轉為1字節(jié)的char數(shù)據(jù)裁厅。大端取到的是第一個高字節(jié),小端取到的是第二個低字節(jié)侨艾。

#include<stdio.h>

int main()
{
    short x = 1; //0x0001
    char *p = (char *)&x;

    if(*p)
    {
        printf("little\n");
    }
    else
    {
        printf("large\n");
    }
    return 0;
}

UTF-32

詳細的本人沒看懂执虹,實際中沒有用到這個編碼,這個編碼使用4字節(jié)存儲唠梨。也有大小端之分

總結

  1. 字符編碼就是把可讀的字符轉化為二進制數(shù)據(jù)方法袋励,字符解碼就是把二進制數(shù)據(jù)轉化為可讀的方法。
  2. ASCII占用1個字節(jié)姻成,只有128個字符插龄,主要是英文字符。
  3. UNICODE是國際標準編碼字符集科展,包含了所有已知符號均牢。
  4. UTF-8是UNICODE編碼的一種實現(xiàn)方式,兼容ASCII碼才睹,也就是英文字符占1個字節(jié)徘跪,漢字可能占兩個字節(jié)或三個字節(jié)。
  5. UTF-16也是UNICODE編碼的一種實現(xiàn)方式琅攘,通常和UNICODE編碼一致垮庐,占用兩個字節(jié),分大小端坞琴。

Base64編碼

Base64編碼的作用是把非ASCII的字符轉換為ASCII的字符哨查。很多加密算法,很喜歡做一次Base64轉換剧辐。原因是使用Base64編碼后寒亥,所有的數(shù)據(jù)都是ASCII字符,方便在網(wǎng)絡上傳輸荧关。

設計思路是:Base64把每三個8Bit的字節(jié)轉換為四個6Bit的字節(jié)(3*8 = 4*6 = 24)溉奕,然后把6Bit再添兩位高位0,組成四個8Bit的字節(jié)忍啤。所以Base64算法生成的數(shù)據(jù)會比原數(shù)據(jù)大1/3左右加勤。

比如:

  1. 圖片這種二進制數(shù)據(jù)就可以轉換為Base64作為文本傳輸。
  2. 比如有中文的數(shù)據(jù)同波,可以通過Base64轉為可以顯示的ASCII數(shù)據(jù)

簡單說明:

  1. 將字符按照文字編碼轉化為二進制字節(jié)鳄梅。
  2. 每3字節(jié)化為一組(24bit),如果字節(jié)不夠未檩,最后輸出結果補=卫枝。然后再把每一組拆分成4個組,每個組6bit,如果不足6bit后面補0讹挎。
  3. 將每個6bit前面補足兩個0校赤,湊夠8位。
  4. 然后按照新分出來的每8位轉成10進制數(shù)筒溃,按照表里面的查找马篮,轉為對應的ASCII字符。
base_64.png

舉例:

字符bl如何轉化為Base64編碼:

  1. bl對應的ASCII碼為: 0110001001101100,因為只有兩個怜奖,所以有一個輸出結果是=
  2. 按照每三個字節(jié)分組:0110001001101100
  3. 按照每個組6bit分4個組,不足6位的補0:011000,100110,110000
  4. 在前面補0,湊夠8位:00011000,00100110,00110000
  5. 轉為10進制:24,38,48浑测。
  6. 查表得到:Y,m,w
  7. 最后補=,所以結果為Ymw=

標準的程序實現(xiàn)可以參考:GTMBase64.m

說明:

Base64是一種編碼算法歪玲,不是加密算法,他的作用不是加密迁央,而是用最簡的ASCII碼來傳輸文本數(shù)據(jù),屏蔽掉設備網(wǎng)絡差異滥崩,是為了方便傳輸?shù)囊环N算法岖圈。很多加密算法,最后生成的是二進制數(shù)據(jù)钙皮,不是可見字符蜂科,而傳輸?shù)囊话闶峭ㄟ^字符傳輸,所以常見的二進制轉化方式就是Base64算法短条。

關于Base64編碼后拼接到url上要注意的地方

Base64編碼后會有+\,這些如果拼接在url作為參數(shù)傳輸?shù)臅r候會被瀏覽器解析為空格和路徑分隔符导匣,導致參數(shù)解析異常,所以有些庫會把+轉義為.或下劃線_茸时。
同時建議做完Base64贡定,同時自動做一次URLEncode 。 這樣+會被轉義為%2B可都。服務端收到后先做URLDecode然后做Base64Decode缓待。

哈希散列算法

一個蘿卜一個坑這個俗語形容這個算法很貼切。官方的定義為:

散列(Hash)函數(shù)提供了這一服務汹粤,它對不同長度的輸入消息命斧,產(chǎn)生固定長度的輸出。

安全的哈希算法要滿足下面條件:

  1. 固定長度嘱兼。不同長度的數(shù)據(jù)国葬,生成的固定長度的數(shù)據(jù)
  2. 唯一性。不同的數(shù)據(jù)芹壕,生成的結果一定不同汇四。相同的數(shù)據(jù),每次輸出的結果一定一樣踢涌。
  3. 不可逆通孽。對于生成后的數(shù)據(jù),反推回原數(shù)據(jù)睁壁,通過算法是不可能的背苦。
  4. 防篡改互捌。兩個輸出的散列值相同,則原數(shù)據(jù)一定相同行剂。如果兩個輸出的散列值不同失暂,則原數(shù)據(jù)一定不同斤斧。

從上面的特點可以知道散列值主要使用的場景:

  1. 生成唯一的值做索引菩掏,比如哈希表
  2. 用作數(shù)據(jù)簽名鲫竞,校驗數(shù)據(jù)完整性和有效性。
  3. 密碼脫敏處理铲觉。

MD5算法

MD5算法是最常用的散列算法澈蝙。

對MD5算法簡要的敘述可以為:MD5以512位分組來處理輸入的信息,且每一分組又被劃分為十六個32位子分組撵幽,經(jīng)過了一系列的處理后灯荧,算法的輸出由4個32位分組組成,將這4個32位分組級聯(lián)后將生成1個128位散列值并齐。

算法有點復雜漏麦,沒有看懂,放下不表况褪。

下面是本人的簡單理解:

  1. MD5算法效率是比較快的撕贞。
  2. MD5防碰撞能力比較強,只有少數(shù)的幾個例子有出現(xiàn)碰撞的情況测垛。但也不影響安全性捏膨。
  3. MD5生成的是固定128位,16個字節(jié)。

MD5算法安全性

目前主流看法是MD5逐漸有被攻克的風險食侮。但是目前還沒有有效算法破解号涯。

主要的破解方法是使用數(shù)據(jù)庫保存常見的字符串的MD5值,然后通過反查得到原始數(shù)據(jù)锯七。也就是如果用戶的密碼很常見就很容易破解链快。如果用戶密碼是隨機的,那就沒什么平臺可以破解了眉尸。

下面對于是用MD5的觀點:

  1. MD5不是加密算法域蜗,重要的用戶密碼應該加密存儲。做MD5只是為了脫敏噪猾,也就是不讓相關人員知道原文是什么(包括內(nèi)鬼)霉祸。
  2. 極重要數(shù)據(jù)是用更安全的算法:比如用戶密碼數(shù)據(jù)使用更安全的算法,比如SHA1算法袱蜡。傳輸過程中也進一步加密丝蹭。
  3. 如果使用MD5算法,在原始值里面加入鹽值坪蚁。鹽值要盡量隨機奔穿。因為如果加入隨機值后原始值也變得隨機镜沽,使用暴力破解就基本不可能了。即result = MD5(password + salt)

關于加鹽

這里有個破解的網(wǎng)站巫橄,大家可以看下常用的策略其實都可以破解淘邻。安全性主要是鹽如何選擇。

  1. 鹽值要是隨機字符湘换,數(shù)據(jù)盡量長一些,只有這樣才能保證最后數(shù)據(jù)的隨機统阿。
  2. 鹽值盡量保證每個用戶不一樣彩倚,增加破解的難度。
  3. 鹽值的保存可以是前后端約定,固化在APP里扶平,但是也應該和用戶相關帆离,比如salt=(固化的值+用戶信息)〗岢危可以是通過一些隨機值變化得來:比如用戶注冊時間等信息做鹽值哥谷。可以是每次隨機生成麻献,當做參數(shù)帶給后端们妥,后端保存密碼+鹽值。安全性從低到高勉吻。還有做多次MD5的监婶,個人覺得意義不大。
  4. 個人推薦的一個方案齿桃。result = MD5 (password + salt)惑惶。salt的計算方法是:MD5(Random(128)+ uid)。其中Random(128)表示一個隨機128位字符串短纵,兩端可以一致带污,固化在代碼里。uid是用戶唯一標示香到,比如登陸用的用戶名鱼冀。這樣對于破解者來說就需要先拿到這個salt值,然后對每個用戶都要生成一個唯一的128位的鹽值养渴,去生成對應的庫雷绢,破解成本就非常高了。

其實目前暴漏出來的是攻擊者把整個數(shù)據(jù)庫的內(nèi)容拿到后理卑,暴力解密出原文翘紊。但是MD5加鹽也好變換也好都是可以通過前端代碼查到算法的,通過算法就可以生成常用數(shù)據(jù)對應的MD5庫藐唠。所以密碼做MD5更重要的是脫敏處理帆疟,不能做為安全的加密使用鹉究,重要的用戶密碼持久化或傳輸過程中一定是要通過加密算法處理的。這樣只要安全保存私鑰就可以了踪宠。在很多金融公司自赔,大量使用硬件加密機做加密處理,然后保存柳琢,更加大了破解難度绍妨。所以如果你的密碼是使用加密再保存的,使用固定鹽值的已經(jīng)可以滿足要求了柬脸。如果擔心可以加上用戶的注冊時間或服務器時間戳做鹽值他去。

SHA1

SHA1也是一種HASH算法。是MD5的替代方案倒堕。生成的數(shù)據(jù)是160位灾测,20個字節(jié)。

目前SHA1也被認為不安全,google找到了算法進行了碰撞,所以普遍推薦使用新的SHA2代替垦巴。Google已經(jīng)開始廢棄這個算法了媳搪。

SHA2

  • SHA-224、SHA-256骤宣、SHA-384秦爆,和SHA-512并稱為SHA-2。
  • 新的散列函數(shù)并沒有接受像SHA-1一樣的公眾密碼社區(qū)做詳細的檢驗涯雅,所以它們的密碼安全性還不被大家廣泛的信任鲜结。
  • 雖然至今尚未出現(xiàn)對SHA-2有效的攻擊,它的算法跟SHA-1基本上仍然相似活逆;因此有些人開始發(fā)展其他替代的散列算法精刷。

所以目前推薦使用SHA2相關的算法做散列算法。

其中SHA-256輸出為256位蔗候,32字節(jié)怒允。
SHA-512輸出為512位,64字節(jié)锈遥。

HMac

HMac是秘鑰相關的哈希算法纫事。和之前的算法不同的在于需要一個秘鑰,才能生成輸出所灸。主要是基于簽名散列算法丽惶。可以認為是散列算法加入了加密邏輯爬立,所以相比SHA算法更難破解钾唬,包含下面的算法。

/*!
    @enum       CCHmacAlgorithm
    @abstract   Algorithms implemented in this module.

    @constant   kCCHmacAlgSHA1      HMAC with SHA1 digest
    @constant   kCCHmacAlgMD5       HMAC with MD5 digest
    @constant   kCCHmacAlgSHA256    HMAC with SHA256 digest
    @constant   kCCHmacAlgSHA384    HMAC with SHA384 digest
    @constant   kCCHmacAlgSHA512    HMAC with SHA512 digest
    @constant   kCCHmacAlgSHA224    HMAC with SHA224 digest
 */
enum {
    kCCHmacAlgSHA1,
    kCCHmacAlgMD5,
    kCCHmacAlgSHA256,
    kCCHmacAlgSHA384,
    kCCHmacAlgSHA512,
    kCCHmacAlgSHA224
};
typedef uint32_t CCHmacAlgorithm;

HMAC主要應用場景:

  1. 密碼的散列存儲,因為需要散列的時候需要密碼抡秆,實際上相當于算法里加了鹽值奕巍。使用的密碼要隨機和用戶相關,請參考鹽值的生產(chǎn)規(guī)則儒士。
  2. 用于數(shù)據(jù)簽名的止。雙方使用共同的秘鑰,然后做簽名驗證着撩。秘鑰可以固化诅福,也可以會話開始前協(xié)商,增加簽名篡改和被破解的難度睹酌。

PS:目前項目中的密碼散列算法权谁,采用的就是HMac算法。

總結

  1. 密碼保存和傳輸需要做散列處理憋沿。但是散列算法主要是脫敏,不能替代加密算法沪猴。
  2. 如今常用的Md5算法和SHA1算法都不再安全辐啄。所以推薦使用SHA-2相關算法。
  3. 散列算法應該加入鹽值即:result=HASH(password+salt)运嗜。其中鹽值應該是隨機字符串且每個用戶不一樣壶辜。
  4. HMac引入了秘鑰的概念,如果不知道秘鑰担租,秘鑰不同砸民,散列值也不同,相當于散列算法加入了鹽值奋救×氩危可以把它當做更安全的散列算法使用。

算法實現(xiàn)

算法都是使用蘋果自己的Security.framework框架實現(xiàn)的尝艘,只需要調(diào)用相關算法就可以了演侯。推薦一個github

//
//  NSData+KKHASH.m
//  SecurityiOS
//
//  Created by cocoa on 16/12/15.
//  Copyright ? 2016年 dev.keke@gmail.com. All rights reserved.
//

#import "NSData+KKHASH.h"
#include <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonHMAC.h>

@implementation NSData (KKHASH)
- (NSData *)hashDataWith:(CCDIGESTAlgorithm )ccAlgorithm
{
    NSData *retData = nil;
    if (self.length <1) {
        return nil;
    }
    
    unsigned char *md;
    
    switch (ccAlgorithm) {
        case CCDIGEST_MD2:
        {
            md = malloc(CC_MD2_DIGEST_LENGTH);
            bzero(md, CC_MD2_DIGEST_LENGTH);
            CC_MD2(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_MD2_DIGEST_LENGTH];
        }
            break;
        case CCDIGEST_MD4:
        {
            md = malloc(CC_MD4_DIGEST_LENGTH);
            bzero(md, CC_MD4_DIGEST_LENGTH);
            CC_MD4(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_MD4_DIGEST_LENGTH];

        }
            break;
        case CCDIGEST_MD5:
        {
            md = malloc(CC_MD5_DIGEST_LENGTH);
            bzero(md, CC_MD5_DIGEST_LENGTH);
            CC_MD5(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_MD5_DIGEST_LENGTH];

        }
            break;
        case CCDIGEST_SHA1:
        {
            md = malloc(CC_SHA1_DIGEST_LENGTH);
            bzero(md, CC_SHA1_DIGEST_LENGTH);
            CC_SHA1(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
            
        }
            break;
        case CCDIGEST_SHA224:
        {
            md = malloc(CC_SHA224_DIGEST_LENGTH);
            bzero(md, CC_SHA224_DIGEST_LENGTH);
            CC_SHA224(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA224_DIGEST_LENGTH];
            
        }
            break;
        case CCDIGEST_SHA256:
        {
            md = malloc(CC_SHA256_DIGEST_LENGTH);
            bzero(md, CC_SHA256_DIGEST_LENGTH);
            CC_SHA256(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA256_DIGEST_LENGTH];
            
        }
            break;
        case CCDIGEST_SHA384:
        {
            md = malloc(CC_SHA384_DIGEST_LENGTH);
            bzero(md, CC_SHA384_DIGEST_LENGTH);
            CC_SHA384(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA384_DIGEST_LENGTH];
            
        }
            break;
        case CCDIGEST_SHA512:
        {
            md = malloc(CC_SHA512_DIGEST_LENGTH);
            bzero(md, CC_SHA512_DIGEST_LENGTH);
            CC_SHA512(self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA512_DIGEST_LENGTH];
            
        }
            break;
            
        default:
            md = malloc(1);
            break;
    }
    
    free(md);
    md = NULL;
    
    return retData;
    
}

- (NSData *)hmacHashDataWith:(CCHmacAlgorithm )ccAlgorithm key:(NSString *)key {
    NSData *retData = nil;
    if (self.length <1) {
        return nil;
    }
    
    unsigned char *md;
    const char *cKey    = [key cStringUsingEncoding:NSUTF8StringEncoding];
    
    switch (ccAlgorithm) {
        case kCCHmacAlgSHA1:
        {
            md = malloc(CC_SHA1_DIGEST_LENGTH);
            bzero(md, CC_SHA1_DIGEST_LENGTH);
            CC_SHA1(self.bytes, (CC_LONG)self.length, md);
            CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
        }
            break;
        case kCCHmacAlgSHA224:
        {
            md = malloc(CC_SHA224_DIGEST_LENGTH);
            bzero(md, CC_SHA224_DIGEST_LENGTH);
            CCHmac(kCCHmacAlgSHA224, cKey, strlen(cKey), self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA224_DIGEST_LENGTH];
            
        }
            break;
        case kCCHmacAlgSHA256:
        {
            md = malloc(CC_SHA256_DIGEST_LENGTH);
            bzero(md, CC_SHA256_DIGEST_LENGTH);
            CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA256_DIGEST_LENGTH];
            
        }
            break;
        case kCCHmacAlgSHA384:
        {
            md = malloc(CC_SHA384_DIGEST_LENGTH);
            bzero(md, CC_SHA384_DIGEST_LENGTH);
            CCHmac(kCCHmacAlgSHA384, cKey, strlen(cKey), self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA384_DIGEST_LENGTH];
            
        }
            break;
        case kCCHmacAlgSHA512:
        {
            md = malloc(CC_SHA512_DIGEST_LENGTH);
            bzero(md, CC_SHA512_DIGEST_LENGTH);
            CCHmac(kCCHmacAlgSHA512, cKey, strlen(cKey), self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_SHA512_DIGEST_LENGTH];
            
        }
            break;
            
        case CCDIGEST_MD5:
        {
            md = malloc(CC_MD5_DIGEST_LENGTH);
            bzero(md, CC_MD5_DIGEST_LENGTH);
            CCHmac(kCCHmacAlgMD5, cKey, strlen(cKey), self.bytes, (CC_LONG)self.length, md);
            retData = [NSData dataWithBytes:md length:CC_MD5_DIGEST_LENGTH];
            
        }
            break;
        default:
            md = malloc(1);
            break;
    }
    
    free(md);
    md = NULL;
    
    return retData;
}


- (NSString *)hexString
{
    NSMutableString *result = nil;
    if (self.length <1) {
        return nil;
    }
    result = [[NSMutableString alloc] initWithCapacity:self.length * 2];
    for (size_t i = 0; i < self.length; i++) {
        [result appendFormat:@"%02x", ((const uint8_t *) self.bytes)[i]];
    }
    return result;
}


+ (NSData *)dataWithHexString:(NSString *)hexString {
    NSMutableData *     result;
    NSUInteger          cursor;
    NSUInteger          limit;
    
    NSParameterAssert(hexString != nil);
    
    result = nil;
    cursor = 0;
    limit = hexString.length;
    if ((limit % 2) == 0) {
        result = [[NSMutableData alloc] init];
        
        while (cursor != limit) {
            unsigned int    thisUInt;
            uint8_t         thisByte;
            
            if ( sscanf([hexString substringWithRange:NSMakeRange(cursor, 2)].UTF8String, "%x", &thisUInt) != 1 ) {
                result = nil;
                break;
            }
            thisByte = (uint8_t) thisUInt;
            [result appendBytes:&thisByte length:sizeof(thisByte)];
            cursor += 2;
        }
    }
    
    return result;
}
@end

對稱加密算法

對稱加密,指雙方使用的秘鑰是相同的背亥。加密和解密都使用這個秘鑰秒际。

對稱加密的優(yōu)點為:

  1. 加密效率高
  2. 加密速度快
  3. 可以對大數(shù)據(jù)進行加密

缺點為:

  1. 秘鑰安全性無法保證,以現(xiàn)在的技術手段來說狡汉,默認對稱秘鑰的秘鑰是非安全的娄徊,可以被拿到的。

加密方法

  • DES :數(shù)據(jù)加密標準盾戴。
    是一種分組數(shù)據(jù)加密技術寄锐,先將數(shù)據(jù)分成固定長度64位的小數(shù)據(jù)塊,之后進行加密。
    速度較快锐峭,適用于大量數(shù)據(jù)加密中鼠。DES密鑰為64位,實際使用56位。將64位數(shù)據(jù)加密成64位數(shù)據(jù)沿癞。
  • 3DES:使用三組密鑰做三次加密援雇。
    是一種基于 DES 的加密算法,使用3個不同密鑰對同一個分組數(shù)據(jù)塊進行3次加密椎扬,如此以使得密文強度更高惫搏。3DES秘鑰為DES兩倍或三倍,即112位或168位蚕涤。其實就是DES的秘鑰加強版筐赔。
  • AES :高級加密標準。
    是美國聯(lián)邦政府采用的一種區(qū)塊加密標準揖铜。
    相較于 DES 和 3DES 算法而言茴丰,AES 算法有著更高的速度和資源使用效率,安全級別也較之更高了天吓,被稱為下一代加密標準贿肩。AES秘鑰長度為128、192龄寞、256位汰规。

使用到的基礎數(shù)學方法:

  • 移位和循環(huán)移位
      移位就是將一段數(shù)碼按照規(guī)定的位數(shù)整體性地左移或右移。循環(huán)右移就是當右移時物邑,把數(shù)碼的最后的位移到數(shù)碼的最前頭溜哮,循環(huán)左移正相反。例如色解,對十進制數(shù)碼12345678循環(huán)右移1位(十進制位)的結果為81234567茂嗓,而循環(huán)左移1位的結果則為23456781。
  • 置換
      就是將數(shù)碼中的某一位的值根據(jù)置換表的規(guī)定冒签,用另一位代替在抛。它不像移位操作那樣整齊有序,看上去雜亂無章萧恕。這正是加密所需,被經(jīng)常應用刚梭。
  • 擴展
      就是將一段數(shù)碼擴展成比原來位數(shù)更長的數(shù)碼。擴展方法有多種,例如,可以用置換的方法票唆,以擴展置換表來規(guī)定擴展后的數(shù)碼每一位的替代值朴读。
  • 壓縮
      就是將一段數(shù)碼壓縮成比原來位數(shù)更短的數(shù)碼。壓縮方法有多種走趋,例如衅金,也可以用置換的方法,以表來規(guī)定壓縮后的數(shù)碼每一位的替代值。
  • 異或
      這是一種二進制布爾代數(shù)運算氮唯。異或的數(shù)學符號為⊕ 鉴吹,它的運算法則如下:
    1⊕1 = 0
    0⊕0 = 0
    1⊕0 = 1
    0⊕1 = 1
      也可以簡單地理解為,參與異或運算的兩數(shù)位如相等惩琉,則結果為0豆励,不等則為1。
  • 迭代
      迭代就是多次重復相同的運算瞒渠,這在密碼算法中經(jīng)常使用良蒸,以使得形成的密文更加難以破解。

對于對稱加密來說伍玖,有幾個共同要點:

  1. 密鑰長度嫩痰;(關系到密鑰的強度)
  2. 加密模式;(ecb窍箍、cbc等等)
  3. 塊加密算法里的塊大小和填充方式區(qū)分串纺;

加密模式

ECB 模式

ECB :電子密本方式,最古老,最簡單的模式椰棘,將加密的數(shù)據(jù)分成若干組造垛,每組的大小跟加密密鑰長度相同;
然后每組都用相同的密鑰加密晰搀。OC對應的為kCCOptionECBMode

ECB的特點為:

  • 每次Key、明文办斑、密文的長度都必須是64位外恕;
  • 數(shù)據(jù)塊重復排序不需要檢測;
  • 相同的明文塊(使用相同的密鑰)產(chǎn)生相同的密文塊乡翅,容易遭受字典攻擊鳞疲;
  • 一個錯誤僅僅會對一個密文塊產(chǎn)生影響,所以支持并行計算蠕蚜;

CBC模式

  • CBC :密文分組鏈接方式尚洽。與ECB相比,加入了初始向量IV靶累。將加密的數(shù)據(jù)分成若干組腺毫,加密時第一個數(shù)據(jù)需要先和向量異或之后才加密。后面的數(shù)據(jù)需要先和前面的數(shù)據(jù)異或挣柬,然后再加密潮酒。是OC默認的加密模式。

CBC的特點為:

  • 每次加密的密文長度為64位(8個字節(jié));
  • 當相同的明文使用相同的密鑰和初始向量的時候CBC模式總是產(chǎn)生相同的密文;
  • 密文塊要依賴以前的操作結果,所以邪蛔,密文塊不能進行重新排列;
  • 可以使用不同的初始化向量來避免相同的明文產(chǎn)生相同的密文,一定程度上抵抗字典攻擊;
  • 一個錯誤發(fā)生以后,當前和以后的密文都會被影響;

塊大小和填充方式

對稱算法的第一步就是對數(shù)據(jù)進行分組急黎,每一個組的大小稱為快大小,比如DES需要將數(shù)據(jù)分組為64位(8個字節(jié)),如果數(shù)據(jù)不夠64位就需要進行補位勃教。

PKCS7Padding填充

OC中指定的填充方法只有kCCOptionPKCS7Padding淤击,對應JAVA的PKCS5Padding填充方式。算法為計算缺幾位數(shù)故源,然后就補幾位數(shù)污抬,數(shù)值為下面的公式:

value=k - (l mod k) ,K=塊大小,l=數(shù)據(jù)長度心软,如果l=8, 則需要填充額外的8個byte的8

比如塊大小為8字節(jié)壕吹,數(shù)據(jù)為DD DD DD DD4個字節(jié),帶入公式删铃,l=4,k=8,計算 8 - (4 mod 8)= 4 ,所以補充4個4耳贬,補位后得到DD DD DD DD 04 04 04 04

唯一特別的是如果最后位數(shù)是夠的猎唁,也需要額外補充咒劲,比如數(shù)據(jù)是DD DD DD DD DD DD DD DD8個字節(jié),帶入公式,l=8,k=8,計算 8 - (8 mod 8)= 8诫隅,所以補位后得到DD DD DD DD DD DD DD DD 08 08 08 08 08 08 08 08腐魂。 所以如果考慮補位,實際輸出buffer大小要加上快大小逐纬,防止buffer不夠蛔屹。

Zero Padding(No Padding)

補位的算法和PKCS7Padding一致,只不過補的位為0x00,比如數(shù)據(jù)為DD DD DD DD4個字節(jié)豁生,帶入公式兔毒,l=4,k=8,計算 8 - (4 mod 8)= 4 ,所以補充4個00,補位后得到DD DD DD DD 00 00 00 00甸箱。

非常不建議用這種模式育叁,因為解密后的數(shù)據(jù)會多出補的00。如果原始數(shù)據(jù)以00結尾(ASCII碼代表空字符)芍殖,就沒辦法區(qū)分出來了豪嗽。

幾種算法比較

算法 秘鑰長度(字節(jié)) 分組長度(字節(jié)) 加密效率 破解難度
DES 8 8 較快(22.5MB/S) 簡單
3DES 24 8 慢(12MB/S)
AES 16/24/32 16 快(51.2MB/s)

IOS 代碼實現(xiàn)解析

下面以AES代碼實現(xiàn)為例,說明下IOS加解密算法的實現(xiàn)豌骏。

+ (NSString *)AES128Encrypt:(NSString *)plainText key:(NSString *)gkey iv:(NSString *)gIv padding:(BOOL)padding
{
    //先處理秘鑰龟梦,如果秘鑰不夠算法長度,就用0填充肯适,如果長于算法長度就截斷变秦。
    char keyPtr[kCCKeySizeAES128+1]; //申請秘鑰buffer,這里根據(jù)不同算法導入需要的key長度框舔。AES128是16個字節(jié)蹦玫,對應的值kCCKeySizeAES128赎婚。
    memset(keyPtr, 0, sizeof(keyPtr)); //使用0填充,保證秘鑰長度達到要求樱溉。
    [gkey getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; //將傳入的秘鑰copy進秘鑰buffer里
    
    //注意這個只在模式為CBC下有效挣输,
    //處理向量值,默認模式為CBC福贞。如果指定了kCCOptionECBMode模式撩嚼,就不需要這個向量。
    char ivPtr[kCCBlockSizeAES128+1]; //申請向量的buffer挖帘,長度為塊長度完丽。AES128塊長度為kCCBlockSizeAES128。
    memset(ivPtr, 0, sizeof(ivPtr));
    [gIv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding]; //將傳入的值copy進向量buffer
    
    
    NSData* data = [plainText dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger dataLength = [data length];
    
    //注意這個只在不指定padding的情況下有效拇舀,需要填充0逻族,算法為num_to_fill= k - (length mod k),如果指定了kCCOptionPKCS7Padding骄崩,就不需要人為填充聘鳞。
    
    long long newSize = dataLength;
    int diff = padding ? 0 : kCCKeySizeAES128 - (dataLength % kCCKeySizeAES128);
    if(diff > 0) {
        newSize = dataLength + diff;
    }
    char dataPtr[newSize];
    memcpy(dataPtr, [data bytes], [data length]);
    for(int i = 0; i < diff; i++) {
        dataPtr[i + dataLength] = 0x00;
    }
    
    
    //輸出的buffer
    size_t bufferSize = newSize + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    memset(buffer, 0, bufferSize);
    
    size_t numBytesCrypted = 0;
    
    CCOptions option = padding ? kCCOptionPKCS7Padding : 0x0000;
    option = gIv.length > 0 ? option : option | kCCOptionECBMode;
    
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          option,
//                                          0x0000,               //No padding | CBC模式  需要補零且需要iv向量
//                                          kCCOptionPKCS7Padding,  //  kCCOptionPKCS7Padding | CBC模式   需要iv向量
                                          //kCCOptionPKCS7Padding | kCCOptionECBMode, // kCCOptionPKCS7Padding | kCCOptionECBMode 不需要iv向量,也不需要補零
//                                          kCCOptionECBMode, // No padding | kCCOptionECBMode 不需要補零要拂,不需要iv向量
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          ivPtr,
                                          dataPtr,
                                          sizeof(dataPtr),
                                          buffer,
                                          bufferSize,
                                          &numBytesCrypted);
    
    if (cryptStatus == kCCSuccess) {
        NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
        resultData = [resultData base64EncodedDataWithOptions:(NSDataBase64EncodingOptions)0];
        NSString *encryptedString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
        return encryptedString;
    }
    free(buffer);
    return nil;
}

+ (NSString *)AES128Decrypt:(NSString *)encryptText key:(NSString *)gkey iv:(NSString *)gIv padding:(BOOL)padding
{
    //復制秘鑰buffer
    char keyPtr[kCCKeySizeAES128 + 1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [gkey getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    //復制向量buffer
    char ivPtr[kCCBlockSizeAES128 + 1];
    memset(ivPtr, 0, sizeof(ivPtr));
    [gIv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
    NSData *data = [[NSData alloc] initWithBase64EncodedString:encryptText options:0];
    NSUInteger dataLength = [data length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    
    //計算采用哪種模式和填充方式
    CCOptions option = padding ? kCCOptionPKCS7Padding : 0x0000;
    option = gIv.length > 0 ? option : option | kCCOptionECBMode;
    
    size_t numBytesCrypted = 0;
    //解密
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          option,
//                                          0x0000,               //No padding | CBC模式  需要補零且需要iv向量
//                                          kCCOptionPKCS7Padding,  //  kCCOptionPKCS7Padding | CBC模式   需要iv向量
                                          //kCCOptionPKCS7Padding | kCCOptionECBMode, // kCCOptionPKCS7Padding | kCCOptionECBMode 不需要iv向量抠璃,也不需要補零
//                                          kCCOptionECBMode, // No padding | kCCOptionECBMode 不需要補零,不需要iv向量
                                          keyPtr,
                                          kCCBlockSizeAES128,
                                          ivPtr,
                                          [data bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesCrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
        NSString *result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
        if ([result length] > 0 && !padding) {
            //如果是非填充模式脱惰,解析后的數(shù)據(jù)會多出填充的'\0',所以需要去掉搏嗡。
            long byteWithoutZero = numBytesCrypted;
            const char *utf8Str =  [result UTF8String];
            //從后開始掃描,查到需要截斷的長度
            for (long i = byteWithoutZero - 1; i > 0; i --) {
                if (utf8Str[i] != '\0') {
                    break;
                }
                byteWithoutZero --;
            }

            NSString *finalReslut = [[NSString alloc] initWithBytes:utf8Str length:byteWithoutZero encoding:NSUTF8StringEncoding];
            
            return finalReslut;
        }
        
        return result;
    }
    free(buffer);
    return nil;
}

建議和說明

  1. 建議使用CBC模式(kCCOptionECBMode)拉一,填充采用kCCOptionPKCS7Padding彻况。這種使用最廣泛,和PHP舅踪、JAVA(AES/CBC/PKCS5Padding)都適配。聯(lián)調(diào)的時候需要注意兩端是否一致良蛮,不一致是調(diào)不通的抽碌。
  2. 通常數(shù)據(jù)加密后,會做一次Base64編碼進行傳輸决瞳,有些應用也會將數(shù)據(jù)轉為二進制字符串傳輸货徙。
  3. 如果不指定模式,則默認是CBC模式皮胡,需要用到向量IV痴颊。
  4. 如果不指定填充格式,則需要自行補0x00處理屡贺,在解碼后也需要把補的0x00去除掉蠢棱,網(wǎng)上很多資料解碼后沒有去除锌杀,會多出\0

說明和總結

  1. 建議對稱加密使用AES加密泻仙。DES無論安全性和效率都不如AES算法糕再。
  2. 加密建議用kCCOptionPKCS7Padding填充方式,對應的JAVA模式為PKCS5Padding
  3. 如果用CBC模式玉转,需要使用初始向量突想,初始向量兩端應該一致。如果不使用應該指定kCCOptionECBMode究抓。也建議用這個模式猾担,兼容性最好。
  4. 秘鑰應該用隨機數(shù)生成對應的位數(shù)刺下。AES128為16個字節(jié)绑嘹,也就是16個字符。不要用短密碼,比如:111111,這樣真的很蠢怠李。
  5. 對稱加密的安全隱患主要在于秘鑰的保存圾叼。重要會話的秘鑰應該隨機生成,使用非對稱加密來溝通交換秘鑰捺癞,策略可以參考我的另一篇文章IOS應用安全-HTTP/HTTPS網(wǎng)絡安全(一)夷蚊。
  6. 如果秘鑰需要硬編碼到程序里,應該做脫敏運算髓介,比如做位運算進行變形等惕鼓。后面會專門寫怎么解決秘鑰硬編碼問題。

非對稱加密算法

非對稱秘鑰加密算法的特點是:加密和解密使用不同的秘鑰唐础。

非對稱加密需要兩個秘鑰:公開秘鑰和私有秘鑰箱歧。兩個秘鑰是不同的,而且通過公鑰是無法推算出私鑰的一膨,使用公鑰加密的數(shù)據(jù)只有用私鑰解密呀邢。

非對稱算法的特點:

  1. 解決了秘鑰保存的問題。公鑰可以發(fā)布出去豹绪,任何人都可以使用价淌,也不用擔心被人獲取到,只要保證私鑰的安全就可以了瞒津。而對稱加密蝉衣,因為秘鑰相同,客戶端泄露了就不安全了巷蚪。
  2. 加密和解密的效率不高病毡,只適合加解密少量的數(shù)據(jù)。而對稱加密效率要高屁柏。這里有一篇文章對比AES和RSA算法的性能對比啦膜。

RSA算法

RSA是目前最常用的非對稱加密算法有送。

算法原理可以看下這篇文章:RSA算法原理

RSA算法基于一個十分簡單的數(shù)論事實:將兩個大質數(shù)相乘十分容易功戚,但是想要對其乘積進行因式分解卻極其困難娶眷,因此可以將乘積公開作為加密密鑰。RSA的秘鑰長度在2048位,現(xiàn)有的技術手段是無法破解的(實際的可以暴力破解的位數(shù)為768位,也就是768位的大數(shù)才有可能暴力進行因數(shù)分解)宙搬。

RSA算法優(yōu)點:

  1. 算法原理簡單聋伦,我都快看懂了。
  2. 安全性也足夠高,目前沒有證據(jù)和方案可以破解1048位以上秘鑰的RSA算法。

缺點:

  1. 安全性取決于秘鑰長度,推薦的要至少1048位轧铁,但是這么高位數(shù)的秘鑰生成速度很慢,所以沒法做一次會話一次秘鑰旦棉。
  2. 加解密的效率很低齿风,相對于對稱加密,差好幾個量級绑洛,而且也不支持加密長數(shù)據(jù)救斑。

國密算法SM2

中國特有的算法,國家強制要求金融機構使用國密算法真屯。包括SM1/SM2/SM3/SM4脸候。其中SM4為對稱加密算法。SM3是哈希算法绑蔫。SM2為非對稱加密算法运沦。但是國家只給算法原理,沒有給出常用的算法實現(xiàn)配深,所以是件蛋疼的事情携添。

算法我也沒看懂。因為項目中使用到了篓叶,所以做了一些研究薪寓。相關代碼可以參考我的github,IOS SM2開源實現(xiàn)非常少,而且都有些問題澜共,要么基于openSSL,代碼特別大锥腻。要么基于libtommath庫嗦董,但是有一些問題,SM2無法調(diào)通瘦黑。所以兩個結合重新整理的下代碼京革。這個代碼只保證SM2算法有效性奇唤,因為經(jīng)過實際使用過,其他的項目未用到匹摇。

SM2的加密流程

safe_encode_sm2.png

除掉數(shù)學方法咬扇,下面是本人的一些理解:

  1. SM2需要依賴于一個曲線,一般使用國家推薦曲線廊勃。如果曲線不對懈贺,肯定是無法加解密的。曲線參數(shù)

    #define SM2_P     "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"
    #define SM2_A     "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC"
    #define SM2_B     "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"
    #define SM2_N     "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"
    #define SM2_G_X   "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7"
    #define SM2_G_Y   "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
    
  2. SM2公鑰分為兩部分:Pub_x和Pub_y坡垫。每個都是32字節(jié)梭灿,總共是64字節(jié)。私鑰長度現(xiàn)在還不清楚是多少冰悠,有資料說是要32位堡妒,但是文檔里面未提到。字節(jié)數(shù)如果不對說明生成秘鑰算法有問題溉卓。

  3. 輸出數(shù)據(jù)分為3段:C1C2C3,其中C1是64個字節(jié)皮迟,C2和原始數(shù)據(jù)大小相同,即原文是6個字節(jié)桑寨,C2就是6個字節(jié)伏尼,C3是32個字節(jié)。所以總長度是64+32+原文長度(字節(jié))西疤。如果長度不對烦粒,要看下是否是人為添加了其他字段。

  4. 算法涉及到哈希算法代赁,標準是使用SM3的hash算法扰她,SM3的Hash算法生成的字節(jié)為32字節(jié),這個聯(lián)調(diào)的時候一定要保證一致芭碍。

加密步驟說明:

  1. 第一步計算隨機數(shù)徒役,如果這個不是隨機的,是固定的,那后面的結果每次輸出就是唯一的窖壕。
  2. 通過隨機數(shù)rank和曲線的G_x忧勿、G_y、P瞻讽、A五個參數(shù)鸳吸,通過ECC算法C1=[k]G = (x1,y1)生成一個點(x1,y1)。拼接起來就是C1數(shù)據(jù)速勇。C1數(shù)據(jù)應該是64個字節(jié)晌砾。有些算法里面會在前面填充0x04,變成65個字節(jié)
  3. 通過公鑰的P_x和P_y,隨機數(shù)rank,A,P烦磁,通過ECC算法[k]PukeyB = [k](XB,YB) = (x2,y2)計算出(x2,y2)养匈,x2和y2的大小為分別為32字節(jié)
  4. 將上面的(x2,y2)拼接哼勇,然后做KDF(密碼派生算法)計算,輸出原文長度(klen)的t值呕乎。t= KDF(x2||y2, klen),KDF一般使用的是SM3的算法积担。結果t的大小和原文的大小一致。
  5. 然后將t和原文做異或運算猬仁,得到C2,C2的大小和原文一致帝璧。
  6. 然后將(x2,原文,x3)拼接,計算一次SM3的Hash算法,生成的數(shù)據(jù)放入C3中逐虚,C3的大小為32字節(jié)聋溜。
  7. 最后把C1C2C3拼接到一起,長度為64+原文長度+32字節(jié)叭爱。注意撮躁,老的標準為C1C3C2,有些實現(xiàn)的是這種模式。

注:這其中ECC算法是標準算法买雾,大部分第三方實現(xiàn)的都沒有問題把曼。主要是KDF算法和Hash算法會有不同。這個聯(lián)調(diào)的時候需要搞清楚漓穿。

SM2解密流程

流程圖如下:

safe_decode_sm2.png

解密步驟說明

  1. 先判斷C1是否在曲線上嗤军。C1長度為64字節(jié),取數(shù)據(jù)的前64字節(jié)就可以了晃危。所以兩端一定要用同樣的曲線叙赚。
  2. 使用C1的數(shù)據(jù),曲線參數(shù)(A,P),私鑰dA,使用ECC算法生成(x2,y2)僚饭,dA*C1 = dA*(x2,y2) = dA*[k]*(Xg,Yg)
  3. 使用(x2,y2)和C2的長度(總長度-64-32)震叮,使用KDF計算t。
  4. 使用c2異或t鳍鸵,達到M’
  5. 計算(x2,M’,y2)的hash值U苇瓣。
  6. 比較U和C3數(shù)據(jù)是否是一致的,如果一致就輸出M’

KDF算法說明:

文檔里的描述

密鑰派生函數(shù)的作用是從一個共享的秘密比特串中派生出密鑰數(shù)據(jù)偿乖。在密鑰協(xié)商過程中击罪,密鑰派
生函數(shù)作用在密鑰交換所獲共享的秘密比特串上,從中產(chǎn)生所需的會話密鑰或進一步加密所需的密鑰
數(shù)據(jù)贪薪。
密鑰派生函數(shù)需要調(diào)用密碼雜湊函數(shù)媳禁。
設密碼雜湊函數(shù)為Hv( ),其輸出是長度恰為v比特的雜湊值画切。
密鑰派生函數(shù)KDF(Z, klen):
輸入:比特串Z竣稽,整數(shù)klen(表示要獲得的密鑰數(shù)據(jù)的比特長度,要求該值小于(232-1)v)。
輸出:長度為klen的密鑰數(shù)據(jù)比特串K丧枪。
a)初始化一個32比特構成的計數(shù)器ct=0x00000001;
b)對i從1到?klen/v?執(zhí)行:
b.1)計算Hai=Hv(Z ∥ ct)庞萍;
b.2) ct++拧烦;
c)若klen/v是整數(shù),令Ha!?klen/v? = Ha?klen/v?钝计,否則令Ha!?klen/v?為Ha?klen/v?最左邊的(klen ?
(v × ?klen/v?))比特恋博;
d)令K = Ha1||Ha2|| · · · ||Ha?klen/v??1||Ha!?klen/v?。

簡化下說明:

  1. 先分組,分組的大小為klen/v,向上取整,其中klen是數(shù)據(jù)長度私恬,v是HASH算法輸出長度债沮。SM3的輸出長度為32字節(jié)。
  2. 然后每一組循環(huán)本鸣,把原始數(shù)據(jù)Z和計數(shù)器ct拼接疫衩,做SM3_Hash運算得到Hai。然后計數(shù)器ct+1荣德。
  3. 最終生成的數(shù)據(jù)Ha1,Ha2…拼接起來闷煤,然后截斷到klen長度也就是數(shù)據(jù)長度。

HASH算法說明

官方使用的是SM3密碼雜湊算法涮瞻,輸入為小于2的64次方bit鲤拿,輸出為256bit(32字節(jié))

總結:

  1. 國密算法的基礎是使用曲線計算署咽。曲線應該使用官方推薦的曲線近顷,曲線不同加解密肯定失敗。
  2. 國密算法生成的數(shù)據(jù)為C1C2C3,其中C1為固定的64字節(jié)宁否,c2和原始數(shù)據(jù)一樣長窒升,C3為固定的32字節(jié)。有些要求數(shù)據(jù)前面加上’0x04’,舊的版本輸出是C3C1C2家淤,這兩點要注意异剥。
  3. 公鑰分為P_x和P_y,都是32字節(jié)長度。私鑰長度從資料上看沒有限制絮重,是一個隨機數(shù)[1,N-2]冤寿。N為曲線參數(shù)。
  4. 加密過程中使用了SM3的散列算法(官方叫雜湊算法)青伤,這個算法輸出為32字節(jié)的數(shù)據(jù)督怜。如果對端沒有用這個算法,兩端也無法加解密成功狠角。

總結

  1. 字符編碼是為了把可見字符和二進制之間做一層轉化号杠。其中UNICODE編碼是國際編碼標準。UTF-8是這種編碼格式的實現(xiàn)方式。特點是ASCII碼的字符占用一個字節(jié)姨蟋,其他的比如中文字符占用兩到三個字符屉凯。
  2. Base64也是一種編碼方式,主要用于把二進制數(shù)據(jù)轉化為ASCII字符眼溶,方便傳輸∮蒲猓現(xiàn)在很多加密算法習慣在加密后把二進制數(shù)做一次Base64進行傳輸。相對于原文堂飞,長度會多出1/3灌旧。也有把二進制轉為字符串的形式,不過長度是原文的2倍绰筛。
  3. 哈希散列算法枢泰,主要用于脫敏處理和信息簽名防篡改,做哈希運算應該加鹽處理铝噩。鹽值應該是隨機值衡蚂,而且和用戶相關,建議使用(隨機數(shù) + 用戶名)薄榛。
  4. 對稱加密兩端秘鑰相同讳窟,加密速度快,可以加密大數(shù)據(jù)敞恋,但是秘鑰保存一直是個難題丽啡。
  5. 非對稱加密分為公鑰和私鑰,公鑰可以公開硬猫。加密速度慢补箍,只能加密小數(shù)據(jù),但是只需要妥善保存私鑰就可以了啸蜜。

通常一個信息加密傳輸流程為:

  1. 雙方約定好使用的編碼格式坑雅。通常常用的是UTF-8編碼。
  2. 客戶端隨機生成對稱秘鑰作為會話秘鑰衬横。使用非對稱加密傳輸給后端裹粤,后端保存這個對稱秘鑰用于之后的加解密過程。
  3. 用戶使用對稱加密(通常為AES)加密整個數(shù)據(jù)蜂林,結果通常使用Base64做編碼(通常還要做一次URLEncode操作),整個相關數(shù)據(jù)按照規(guī)則使用Hash算法(通常為SHA256算法)做數(shù)據(jù)簽名遥诉。最后做傳輸
  4. 如果是用戶密碼的話建議用HMac做Hash脫敏處理,然后單獨使用非對稱加密進一步加強安全性噪叙。

參考:

  1. 字符編碼筆記:ASCII矮锈,Unicode和UTF-8
  2. 百度百科-ASCII
  3. 深入淺出大小端
  4. Base64 編碼
  5. MD5+Salt安全淺析
  6. 哈希加密算法 MD5,SHA-1,SHA-2,SHA-256,SHA-512,SHA-3,RIPEMD-160 - aTool
  7. DES加密模式詳解
  8. DES加密算法原理
  9. 關于PKCS5Padding與PKCS7Padding的區(qū)別
  10. 各種加密算法比較
  11. AES在線加解密
  12. iOS - Safe iOS 加密安全
  13. RSA算法原理
  14. SM2國密算法官方說明
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市睁蕾,隨后出現(xiàn)的幾起案子苞笨,更是在濱河造成了極大的恐慌债朵,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瀑凝,死亡現(xiàn)場離奇詭異序芦,居然都是意外死亡,警方通過查閱死者的電腦和手機粤咪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門芝加,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人射窒,你說我怎么就攤上這事〗埽” “怎么了脉顿?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長点寥。 經(jīng)常有香客問我艾疟,道長,這世上最難降的妖魔是什么敢辩? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任蔽莱,我火速辦了婚禮,結果婚禮上戚长,老公的妹妹穿的比我還像新娘盗冷。我一直安慰自己,他們只是感情好同廉,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布仪糖。 她就那樣靜靜地躺著,像睡著了一般迫肖。 火紅的嫁衣襯著肌膚如雪锅劝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蟆湖,我揣著相機與錄音故爵,去河邊找鬼。 笑死隅津,一個胖子當著我的面吹牛诬垂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饥瓷,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剥纷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呢铆?” 一聲冷哼從身側響起晦鞋,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悠垛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體线定,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年确买,在試婚紗的時候發(fā)現(xiàn)自己被綠了斤讥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡湾趾,死狀恐怖芭商,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搀缠,我是刑警寧澤铛楣,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站艺普,受9級特大地震影響簸州,放射性物質發(fā)生泄漏。R本人自食惡果不足惜歧譬,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一岸浑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瑰步,春花似錦矢洲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舌界,卻和暖如春掘譬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呻拌。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工葱轩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藐握。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓靴拱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猾普。 傳聞我的和親對象是個殘疾皇子袜炕,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容