SHA1摘要算法原理以及代码实现_HAO

SHA1摘要算法原理以及代码实现

1. 术语解释:

SHA1算法需要用到一系列的位运算,以下介绍位运算的符号表示:

XOR   异或    A XOR B

OR             A  OR  B

AND          A  AND B

NOT          NOT A

<<    左移     A>>nn为常数,下同)

>>    右移     A<<n

另外这里还需要用到一种较为复杂的位运算,我们称之为循环左移:

Sn(X)= (X<<n)OR(X>>32-n) 循环左移

例如,1001循环左移1位:  S1(1001)=0011

即将数据左移1位,然后用左边溢出的位去填补右边需要补充的位。

 

2. 数据前期处理:

当我们得到一个消息信息,需要对其进行一系列的处理,主要有补位、补长度及分块。

  1)补位:令我们得到的消息为字符串"bob",我们先以字符为单位将其转换为十六进制Ascii码的排列。b--62,o--6fb--62。即现在的消息信息为626f62。再将其转换为二进制排列,可得:01100010 01101111 01100010。可见,这是一个长度为24位的数据。现在进行补位操作,首先在我们的数据后加上一个1。得到:01100010 01101111 01100010 1。目前的长度L=25,计算长度L512的取余运算,及X=L%512,若X不等于448,就在数据末尾加上一个0,一直循环到X=448为止。此时数据的长度应为512n倍加上448,即L'=512*n+448。补位结束。

  2)补长度与分块:现在我们得到一个长度L512*n+448位的二进制字符串,我们在这个二进制字符串的末尾添加上一个长度为64位的二进制字符串,用来表示原消息数据的长度,如上文中的"bob"长度为24位,其二进制表示:

    00000000 00000000 00000000 00000000

    00000000 00000000 00000000 00011000

  如此,我们最终得到一个长度L''=512*n(即长度为512的倍数)的二进制字符串。最后,我们判断这个二进制字符串的长度是否大于512(n是否为1)。若大于512位,我们则需要将其分割为长度为512位的多个字符串,这里用M[i](M[0]M[1]……)表示。若不大于,则直接用M0保存。到此,补长度与分块结束。

3.算法需要的各常量的值及函数表达式:

  1)常量:

     Kt = 0x5A827999 (0 <= t <= 19)

     Kt = 0x6ED9EBA1 (20 <= t <= 39)

     Kt = 0x8F1BBCDC (40 <= t <= 59)

     Kt = 0xCA62C1D6 (60 <= t <= 79)

  2)函数表达式:

    ft(B,C,D) = (B AND C) OR ((NOT B) AND D)              ( 0 <= t <= 19)

    ft(B,C,D) = B XOR C XOR D                        (20 <= t <= 39)

    ft(B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)

    ft(B,C,D) = B XOR C XOR D                     (60 <= t <= 79).

4.算法:

    1)缓冲区:

        处理上述的二进制字符串需要一些缓冲区,下面我们详细列出各缓冲区的规格:

            a.32位的缓冲区5个:A,B,C,D,E

            b.32位的缓冲区80个:W[0]~W[79]

            c.32位的缓冲区5个:H[0]~H[4]

            d.32位的缓冲区1个:TEMP

        首先我们将缓冲区H[]进行初始化赋值:

              H[0]=0x67452301

              H[1]=0xEFCDAB89

              H[2]=0x98BADCFE

              H[3]=0x10325476

              H[4]=0xC3D2E1F0

    2)针对每个M[i]进行循环:

               a.M[i]从左到右分割为1632位的字符串,转换为uint型的数值分别存储在缓冲区W[0]~W[15]中。

               b.对于W[16]~W[79],我们进行如下循环:

                        W[i] = S1(W[i-3] XOR W[i-8] XOR W[i- 14] XOR W[i-16])

                   至此,我们的W[]缓冲区已经全部赋值完毕。

               c.对缓冲区A,B,C,D,E分别进行赋值:

                       A=H[0]

                       B=H[1]

                       C=H[2]

                       D=H[3]

                        E=H[4]

                d.对于W[0]~W[79],我们再进行如下循环:

                   TEMP = S5(A) + ft(B,C,D) + E + W[i] + K[i]

                    E=D

                    D=C

                    C=S30(B)

                    B=A

                    A=TEMP

                e.我们再对缓冲区H[]进行操作:

                    H[0]=H[0]+A

                    H[1]=H[1]+B

                    H[2]=H[2]+C

                    H[3]=H[3]+D

                    H[4]=H[4]+E

     3)如此完成每一个M[i]的循环后,最终得到的消息摘要为:

                   H[0] H[1] H[2] H[3] H[4]

        此处缓冲区H[]中的数值全部转换为16进制数用字符串形式表示,以上述格式排列即为我们最终计算出的消息摘要。

 

最后贴上SHA-1算法实现源代码:

SHA1.h

[cpp] view plain copy

1. #ifndef SHA1_H_A545E61D43E9404E8D736869AB3CBFE7  

2. #define SHA1_H_A545E61D43E9404E8D736869AB3CBFE7  

3.   

4. #if !defined(SHA1_UTILITY_FUNCTIONS) && !defined(SHA1_NO_UTILITY_FUNCTIONS)  

5. #define SHA1_UTILITY_FUNCTIONS  

6. #endif  

7.   

8. #if !defined(SHA1_STL_FUNCTIONS) && !defined(SHA1_NO_STL_FUNCTIONS)  

9. #define SHA1_STL_FUNCTIONS  

10. #if !defined(SHA1_UTILITY_FUNCTIONS)  

11. #error STL functions require SHA1_UTILITY_FUNCTIONS.  

12. #endif  

13. #endif  

14.   

15. #include <memory.h>  

16. #include <limits.h>  

17.   

18. #ifdef SHA1_UTILITY_FUNCTIONS  

19. #include <stdio.h>  

20. #include <string.h>  

21. #endif  

22.   

23. #ifdef SHA1_STL_FUNCTIONS  

24. #include <string>  

25. #endif  

26.   

27. #ifdef _MSC_VER  

28. #include <stdlib.h>  

29. #endif  

30.   

31. // You can define the endian mode in your files without modifying the SHA-1  

32. // source files. Just #define SHA1_LITTLE_ENDIAN or #define SHA1_BIG_ENDIAN  

33. // in your files, before including the SHA1.h header file. If you don't  

34. // define anything, the class defaults to little endian.  

35. #if !defined(SHA1_LITTLE_ENDIAN) && !defined(SHA1_BIG_ENDIAN)  

36. #define SHA1_LITTLE_ENDIAN  

37. #endif  

38.   

39. // If you want variable wiping, #define SHA1_WIPE_VARIABLES, if not,  

40. // #define SHA1_NO_WIPE_VARIABLES. If you don't define anything, it  

41. // defaults to wiping.  

42. #if !defined(SHA1_WIPE_VARIABLES) && !defined(SHA1_NO_WIPE_VARIABLES)  

43. #define SHA1_WIPE_VARIABLES  

44. #endif  

45.   

46. #if defined(SHA1_HAS_TCHAR)  

47. #include <tchar.h>  

48. #else  

49. #ifdef _MSC_VER  

50. #include <tchar.h>  

51. #else  

52. #ifndef TCHAR  

53. #define TCHAR char  

54. #endif  

55. #ifndef _T  

56. #define _T(__x) (__x)  

57. #define _tmain main  

58. #define _tprintf printf  

59. #define _getts gets  

60. #define _tcslen strlen  

61. #define _tfopen fopen  

62. #define _tcscpy strcpy  

63. #define _tcscat strcat  

64. #define _sntprintf snprintf  

65. #endif  

66. #endif  

67. #endif  

68.   

69. ///////////////////////////////////////////////////////////////////////////  

70. // Define variable types  

71.   

72. #ifndef UINT_8  

73. #ifdef _MSC_VER // Compiling with Microsoft compiler  

74. #define UINT_8 unsigned __int8  

75. #else // !_MSC_VER  

76. #define UINT_8 unsigned char  

77. #endif // _MSC_VER  

78. #endif  

79.   

80. #ifndef UINT_32  

81. #ifdef _MSC_VER // Compiling with Microsoft compiler  

82. #define UINT_32 unsigned __int32  

83. #else // !_MSC_VER  

84. #if (ULONG_MAX == 0xFFFFFFFFUL)  

85. #define UINT_32 unsigned long  

86. #else  

87. #define UINT_32 unsigned int  

88. #endif  

89. #endif // _MSC_VER  

90. #endif // UINT_32  

91.   

92. #ifndef INT_64  

93. #ifdef _MSC_VER // Compiling with Microsoft compiler  

94. #define INT_64 __int64  

95. #else // !_MSC_VER  

96. #define INT_64 long long  

97. #endif // _MSC_VER  

98. #endif // INT_64  

99.   

100. #ifndef UINT_64  

101. #ifdef _MSC_VER // Compiling with Microsoft compiler  

102. #define UINT_64 unsigned __int64  

103. #else // !_MSC_VER  

104. #define UINT_64 unsigned long long  

105. #endif // _MSC_VER  

106. #endif // UINT_64  

107.   

108. ///////////////////////////////////////////////////////////////////////////  

109. // Declare SHA-1 workspace  

110.   

111. typedef union  

112. {  

113.     UINT_8 c[64];  

114.     UINT_32 l[16];  

115. } SHA1_WORKSPACE_BLOCK;  

116.   

117. class CSHA1  

118. {  

119. public:  

120. #ifdef SHA1_UTILITY_FUNCTIONS  

121.     // Different formats for ReportHash(Stl)  

122.     enum REPORT_TYPE  

123.     {  

124.         REPORT_HEX = 0,  

125.         REPORT_DIGIT = 1,  

126.         REPORT_HEX_SHORT = 2  

127.     };  

128. #endif  

129.   

130.     // Constructor and destructor  

131.     CSHA1();  

132.   

133. #ifdef SHA1_WIPE_VARIABLES  

134.     ~CSHA1();  

135. #endif  

136.   

137.     void Reset();  

138.   

139.     // Hash in binary data and strings  

140.     void Update(const UINT_8* pbData, UINT_32 uLen);  

141.   

142. #ifdef SHA1_UTILITY_FUNCTIONS  

143.     // Hash in file contents  

144.     bool HashFile(const TCHAR* tszFileName);  

145. #endif  

146.   

147.     // Finalize hash; call it before using ReportHash(Stl)  

148.     void Final();  

149.   

150. #ifdef SHA1_UTILITY_FUNCTIONS  

151.     bool ReportHash(TCHAR* tszReport, REPORT_TYPE rtReportType = REPORT_HEX) const;  

152. #endif  

153.   

154. #ifdef SHA1_STL_FUNCTIONS  

155.     bool ReportHashStl(std::basic_string<TCHAR>& strOut, REPORT_TYPE rtReportType =  

156.         REPORT_HEX) const;  

157. #endif  

158.   

159.     // Get the raw message digest (20 bytes)  

160.     bool GetHash(UINT_8* pbDest20) const;  

161.   

162. private:  

163.     // Private SHA-1 transformation  

164.     void Transform(UINT_32* pState, const UINT_8* pBuffer);  

165.   

166.     // Member variables  

167.     UINT_32 m_state[5];  

168.     UINT_32 m_count[2];  

169.     UINT_32 m_reserved0[1]; // Memory alignment padding  

170.     UINT_8 m_buffer[64];  

171.     UINT_8 m_digest[20];  

172.     UINT_32 m_reserved1[3]; // Memory alignment padding  

173.   

174.     UINT_8 m_workspace[64];  

175.     SHA1_WORKSPACE_BLOCK* m_block; // SHA1 pointer to the byte array above  

176. };  

177.   

178. #endif // SHA1_H_A545E61D43E9404E8D736869AB3CBFE7  

 

SHA1.cpp

[cpp] view plain copy

1. #define _CRT_SECURE_NO_WARNINGS  

2. #include "SHA1.h"  

3.   

4. #define SHA1_MAX_FILE_BUFFER (32 * 20 * 820)  

5.   

6. // Rotate p_val32 by p_nBits bits to the left  

7. #ifndef ROL32  

8. #ifdef _MSC_VER  

9. #define ROL32(p_val32,p_nBits) _rotl(p_val32,p_nBits)  

10. #else  

11. #define ROL32(p_val32,p_nBits) (((p_val32)<<(p_nBits))|((p_val32)>>(32-(p_nBits))))  

12. #endif  

13. #endif  

14.   

15. #ifdef SHA1_LITTLE_ENDIAN  

16. #define SHABLK0(i) (m_block->l[i] = \  

17.     (ROL32(m_block->l[i],24) & 0xFF00FF00) | (ROL32(m_block->l[i],8) & 0x00FF00FF))  

18. #else  

19. #define SHABLK0(i) (m_block->l[i])  

20. #endif  

21.   

22. #define SHABLK(i) (m_block->l[i&15] = ROL32(m_block->l[(i+13)&15] ^ \  

23.     m_block->l[(i+8)&15] ^ m_block->l[(i+2)&15] ^ m_block->l[i&15],1))  

24.   

25. // SHA-1 rounds  

26. #define S_R0(v,w,x,y,z,i) {z+=((w&(x^y))^y)+SHABLK0(i)+0x5A827999+ROL32(v,5);w=ROL32(w,30);}  

27. #define S_R1(v,w,x,y,z,i) {z+=((w&(x^y))^y)+SHABLK(i)+0x5A827999+ROL32(v,5);w=ROL32(w,30);}  

28. #define S_R2(v,w,x,y,z,i) {z+=(w^x^y)+SHABLK(i)+0x6ED9EBA1+ROL32(v,5);w=ROL32(w,30);}  

29. #define S_R3(v,w,x,y,z,i) {z+=(((w|x)&y)|(w&x))+SHABLK(i)+0x8F1BBCDC+ROL32(v,5);w=ROL32(w,30);}  

30. #define S_R4(v,w,x,y,z,i) {z+=(w^x^y)+SHABLK(i)+0xCA62C1D6+ROL32(v,5);w=ROL32(w,30);}  

31.   

32. #pragma warning(push)  

33. // Disable compiler warning 'Conditional expression is constant'  

34. #pragma warning(disable: 4127)  

35.   

36. CSHA1::CSHA1()  

37. {  

38.     m_block = (SHA1_WORKSPACE_BLOCK*)m_workspace;  

39.   

40.     Reset();  

41. }  

42.   

43. #ifdef SHA1_WIPE_VARIABLES  

44. CSHA1::~CSHA1()  

45. {  

46.     Reset();  

47. }  

48. #endif  

49.   

50. void CSHA1::Reset()  

51. {  

52.     // SHA1 initialization constants  

53.     m_state[0] = 0x67452301;  

54.     m_state[1] = 0xEFCDAB89;  

55.     m_state[2] = 0x98BADCFE;  

56.     m_state[3] = 0x10325476;  

57.     m_state[4] = 0xC3D2E1F0;  

58.   

59.     m_count[0] = 0;  

60.     m_count[1] = 0;  

61. }  

62.   

63. void CSHA1::Transform(UINT_32* pState, const UINT_8* pBuffer)  

64. {  

65.     UINT_32 a = pState[0], b = pState[1], c = pState[2], d = pState[3], e = pState[4];  

66.   

67.     memcpy(m_block, pBuffer, 64);  

68.   

69.     // 4 rounds of 20 operations each, loop unrolled  

70.     S_R0(a,b,c,d,e, 0); S_R0(e,a,b,c,d, 1); S_R0(d,e,a,b,c, 2); S_R0(c,d,e,a,b, 3);  

71.     S_R0(b,c,d,e,a, 4); S_R0(a,b,c,d,e, 5); S_R0(e,a,b,c,d, 6); S_R0(d,e,a,b,c, 7);  

72.     S_R0(c,d,e,a,b, 8); S_R0(b,c,d,e,a, 9); S_R0(a,b,c,d,e,10); S_R0(e,a,b,c,d,11);  

73.     S_R0(d,e,a,b,c,12); S_R0(c,d,e,a,b,13); S_R0(b,c,d,e,a,14); S_R0(a,b,c,d,e,15);  

74.     S_R1(e,a,b,c,d,16); S_R1(d,e,a,b,c,17); S_R1(c,d,e,a,b,18); S_R1(b,c,d,e,a,19);  

75.     S_R2(a,b,c,d,e,20); S_R2(e,a,b,c,d,21); S_R2(d,e,a,b,c,22); S_R2(c,d,e,a,b,23);  

76.     S_R2(b,c,d,e,a,24); S_R2(a,b,c,d,e,25); S_R2(e,a,b,c,d,26); S_R2(d,e,a,b,c,27);  

77.     S_R2(c,d,e,a,b,28); S_R2(b,c,d,e,a,29); S_R2(a,b,c,d,e,30); S_R2(e,a,b,c,d,31);  

78.     S_R2(d,e,a,b,c,32); S_R2(c,d,e,a,b,33); S_R2(b,c,d,e,a,34); S_R2(a,b,c,d,e,35);  

79.     S_R2(e,a,b,c,d,36); S_R2(d,e,a,b,c,37); S_R2(c,d,e,a,b,38); S_R2(b,c,d,e,a,39);  

80.     S_R3(a,b,c,d,e,40); S_R3(e,a,b,c,d,41); S_R3(d,e,a,b,c,42); S_R3(c,d,e,a,b,43);  

81.     S_R3(b,c,d,e,a,44); S_R3(a,b,c,d,e,45); S_R3(e,a,b,c,d,46); S_R3(d,e,a,b,c,47);  

82.     S_R3(c,d,e,a,b,48); S_R3(b,c,d,e,a,49); S_R3(a,b,c,d,e,50); S_R3(e,a,b,c,d,51);  

83.     S_R3(d,e,a,b,c,52); S_R3(c,d,e,a,b,53); S_R3(b,c,d,e,a,54); S_R3(a,b,c,d,e,55);  

84.     S_R3(e,a,b,c,d,56); S_R3(d,e,a,b,c,57); S_R3(c,d,e,a,b,58); S_R3(b,c,d,e,a,59);  

85.     S_R4(a,b,c,d,e,60); S_R4(e,a,b,c,d,61); S_R4(d,e,a,b,c,62); S_R4(c,d,e,a,b,63);  

86.     S_R4(b,c,d,e,a,64); S_R4(a,b,c,d,e,65); S_R4(e,a,b,c,d,66); S_R4(d,e,a,b,c,67);  

87.     S_R4(c,d,e,a,b,68); S_R4(b,c,d,e,a,69); S_R4(a,b,c,d,e,70); S_R4(e,a,b,c,d,71);  

88.     S_R4(d,e,a,b,c,72); S_R4(c,d,e,a,b,73); S_R4(b,c,d,e,a,74); S_R4(a,b,c,d,e,75);  

89.     S_R4(e,a,b,c,d,76); S_R4(d,e,a,b,c,77); S_R4(c,d,e,a,b,78); S_R4(b,c,d,e,a,79);  

90.   

91.     // Add the working vars back into state  

92.     pState[0] += a;  

93.     pState[1] += b;  

94.     pState[2] += c;  

95.     pState[3] += d;  

96.     pState[4] += e;  

97.   

98.     // Wipe variables  

99. #ifdef SHA1_WIPE_VARIABLES  

100.     a = b = c = d = e = 0;  

101. #endif  

102. }  

103.   

104. void CSHA1::Update(const UINT_8* pbData, UINT_32 uLen)  

105. {  

106.     UINT_32 j = ((m_count[0] >> 3) & 0x3F);  

107.   

108.     if((m_count[0] += (uLen << 3)) < (uLen << 3))  

109.         ++m_count[1]; // Overflow  

110.   

111.     m_count[1] += (uLen >> 29);  

112.   

113.     UINT_32 i;  

114.     if((j + uLen) > 63)  

115.     {  

116.         i = 64 - j;  

117.         memcpy(&m_buffer[j], pbData, i);  

118.         Transform(m_state, m_buffer);  

119.   

120.         for( ; (i + 63) < uLen; i += 64)  

121.             Transform(m_state, &pbData[i]);  

122.   

123.         j = 0;  

124.     }  

125.     else i = 0;  

126.   

127.     if((uLen - i) != 0)  

128.         memcpy(&m_buffer[j], &pbData[i], uLen - i);  

129. }  

130.   

131. #ifdef SHA1_UTILITY_FUNCTIONS  

132. bool CSHA1::HashFile(const TCHAR* tszFileName)  

133. {  

134.     if(tszFileName == NULL) return false;  

135.   

136.     FILE* fpIn = _tfopen(tszFileName, _T("rb"));  

137.     if(fpIn == NULL) return false;  

138.   

139.     UINT_8* pbData = new UINT_8[SHA1_MAX_FILE_BUFFER];  

140.     if(pbData == NULL) { fclose(fpIn); return false; }  

141.   

142.     bool bSuccess = true;  

143.     while(true)  

144.     {  

145.         const size_t uRead = fread(pbData, 1, SHA1_MAX_FILE_BUFFER, fpIn);  

146.   

147.         if(uRead > 0)  

148.             Update(pbData, static_cast<UINT_32>(uRead));  

149.   

150.         if(uRead < SHA1_MAX_FILE_BUFFER)  

151.         {  

152.             if(feof(fpIn) == 0) bSuccess = false;  

153.             break;  

154.         }  

155.     }  

156.   

157.     fclose(fpIn);  

158.     delete[] pbData;  

159.     return bSuccess;  

160. }  

161. #endif  

162.   

163. void CSHA1::Final()  

164. {  

165.     UINT_32 i;  

166.   

167.     UINT_8 pbFinalCount[8];  

168.     for(i = 0; i < 8; ++i)  

169.         pbFinalCount[i] = static_cast<UINT_8>((m_count[((i >= 4) ? 0 : 1)] >>  

170.             ((3 - (i & 3)) * 8) ) & 0xFF); // Endian independent  

171.   

172.     Update((UINT_8*)"\200", 1);  

173.   

174.     while((m_count[0] & 504) != 448)  

175.         Update((UINT_8*)"\0", 1);  

176.   

177.     Update(pbFinalCount, 8); // Cause a Transform()  

178.   

179.     for(i = 0; i < 20; ++i)  

180.         m_digest[i] = static_cast<UINT_8>((m_state[i >> 2] >> ((3 -  

181.             (i & 3)) * 8)) & 0xFF);  

182.   

183.     // Wipe variables for security reasons  

184. #ifdef SHA1_WIPE_VARIABLES  

185.     memset(m_buffer, 0, 64);  

186.     memset(m_state, 0, 20);  

187.     memset(m_count, 0, 8);  

188.     memset(pbFinalCount, 0, 8);  

189.     Transform(m_state, m_buffer);  

190. #endif  

191. }  

192.   

193. #ifdef SHA1_UTILITY_FUNCTIONS  

194. bool CSHA1::ReportHash(TCHAR* tszReport, REPORT_TYPE rtReportType) const  

195. {  

196.     if(tszReport == NULL) return false;  

197.   

198.     TCHAR tszTemp[16];  

199.   

200.     if((rtReportType == REPORT_HEX) || (rtReportType == REPORT_HEX_SHORT))  

201.     {  

202.         _sntprintf(tszTemp, 15, _T("%02X"), m_digest[0]);  

203.         _tcscpy(tszReport, tszTemp);  

204.   

205.         const TCHAR* lpFmt = ((rtReportType == REPORT_HEX) ? _T(" %02X") : _T("%02X"));  

206.         for(size_t i = 1; i < 20; ++i)  

207.         {  

208.             _sntprintf(tszTemp, 15, lpFmt, m_digest[i]);  

209.             _tcscat(tszReport, tszTemp);  

210.         }  

211.     }  

212.     else if(rtReportType == REPORT_DIGIT)  

213.     {  

214.         _sntprintf(tszTemp, 15, _T("%u"), m_digest[0]);  

215.         _tcscpy(tszReport, tszTemp);  

216.   

217.         for(size_t i = 1; i < 20; ++i)  

218.         {  

219.             _sntprintf(tszTemp, 15, _T(" %u"), m_digest[i]);  

220.             _tcscat(tszReport, tszTemp);  

221.         }  

222.     }  

223.     else return false;  

224.   

225.     return true;  

226. }  

227. #endif  

228.   

229. #ifdef SHA1_STL_FUNCTIONS  

230. bool CSHA1::ReportHashStl(std::basic_string<TCHAR>& strOut, REPORT_TYPE rtReportType) const  

231. {  

232.     TCHAR tszOut[84];  

233.     const bool bResult = ReportHash(tszOut, rtReportType);  

234.     if(bResult) strOut = tszOut;  

235.     return bResult;  

236. }  

237. #endif  

238.   

239. bool CSHA1::GetHash(UINT_8* pbDest20) const  

240. {  

241.     if(pbDest20 == NULL) return false;  

242.     memcpy(pbDest20, m_digest, 20);  

243.     return true;  

244. }  

245.   

246. #pragma warning(pop)  

 

猜你喜欢

转载自blog.csdn.net/llzhang_fly/article/details/79975481