PHP的SESSIONID生成的原理
作为一个web程序猿,我们对session肯定都不陌生,session id是我们各自在服务器上的一个唯一标志,这个id串既可以由php自动来生成,也可以由我们来赋予。你们可能和我一样,很关心php自动生成的那个id串是怎么来的,冲突的概率有多大,以及容不容易被别人计算出来,所以有了下文。
我们写一下php5.3.6的源码,进入/ext/session目录,生成session id的函数位于session.c文件的345行,下面详细介绍一下这个函数。
1. PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
2. {
3.
4. //这几行行定义了些散列函数所需的数据,直接越过~
5. PHP_MD5_CTX md5_context;
6. PHP_SHA1_CTX sha1_context;
7.
8. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
9. void *hash_context;
10. #endif
11.
12. unsigned char *digest;
13. int digest_len;
14. int j;
15.
16. char *buf, *outid;
17.
18. zval **array;
19. zval **token;
20.
21. //用来记录$_SERVER['REMOTE_ADDR']的值
22. char *remote_addr = NULL;
23.
24. //一个timeval结构,用来记录当前的时间戳及毫秒数
25. struct timeval tv;
26. gettimeofday(&tv, NULL);
27.
28. //如果可能的话,就对remote_ADDR进行赋值,用php伪代码表示便是:
29. //if(isset($_SERVER['REMOTE_ADDR']))
30. //{remote_addr = $_SERVER['REMOTE_ADDR'];}
31. //备注:在cli模式下是没有的~
32. if (
33. zend_hash_find(
34. &EG(symbol_table),
35. "_SERVER",
36. sizeof("_SERVER"),
37. (void **) &array
38. ) == SUCCESS
39. && Z_TYPE_PP(array) == IS_ARRAY
40. && zend_hash_find(
41. Z_ARRVAL_PP(array),
42. "REMOTE_ADDR",
43. sizeof("REMOTE_ADDR"),
44. (void **) &token
45. ) == SUCCESS
46. )
47. {
48. remote_addr = Z_STRVAL_PP(token);
49. }
50.
51. /* maximum 15+19+19+10 bytes */
52. //生成所需的session id,当然后面还需要后续的处理~
53. //格式为:%.15s%ld%ld%0.8F,每一段的含义如下:
54. //%.15s remote_addr ? remote_addr : "" 这一行很容易理解
55. //%ld tv.tv_sec 当前的时间戳
56. //%ld (long int)tv.tv_usec 当前毫秒数
57. //%0.8F php_combined_lcg(TSRMLS_C) * 10 一个随机数
58. spprintf(
59. &buf,
60. 0,
61. "%.15s%ld%ld%0.8F",
62. remote_addr ? remote_addr : "",
63. tv.tv_sec,
64. (long int)tv.tv_usec,
65. php_combined_lcg(TSRMLS_C) * 10
66. );
67.
68. //下面对buf字符串的值进行散列处理
69. //检测session配置中的散列函数
70. /*
71. 300行: enum{
72. PS_HASH_FUNC_MD5,
73. PS_HASH_FUNC_SHA1,
74. PS_HASH_FUNC_OTHER
75. };
76. 812行:
77. PHP_INI_ENTRY("session.hash_function","0",PHP_INI_ALL,OnUpdateHashFunc)
78. 738行:
79. static PHP_INI_MH(OnUpdateHashFunc)
80. {
81. ......
82. ......
83. val = strtol(new_value, &endptr, 10);
84. if (endptr && (*endptr == '\0'))
85. {
86. /* Numeric value */
87. PS(hash_func) = val ? 1 : 0;
88. return SUCCESS;
89. }
90. ......
91. ......
92. 可知PS(hash_func)的默认值为0,即PS_HASH_FUNC_MD5。
93. */
94.
95. switch (PS(hash_func))
96. {
97. //如果是md5,则用md5算法对我们的buf串进行散列处理。
98. case PS_HASH_FUNC_MD5:
99. PHP_MD5Init(&md5_context);
100. PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
101. digest_len = 16;
102. break;
103.
104. //如果是SHA1,则用SHA1算法对我们的buf串进行散列处理。
105. case PS_HASH_FUNC_SHA1:
106. PHP_SHA1Init(&sha1_context);
107. PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
108. digest_len = 20;
109. break;
110.
111. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
112. case PS_HASH_FUNC_OTHER:
113. if (!PS(hash_ops))
114. {
115. php_error_docref(
116. NULL TSRMLS_CC,
117. E_ERROR,
118. "Invalid session hash function"
119. );
120. efree(buf);
121. return NULL;
122. }
123.
124. hash_context = emalloc(PS(hash_ops)->context_size);
125. PS(hash_ops)->hash_init(hash_context);
126. PS(hash_ops)->hash_update(hash_context, (unsigned char *) buf, strlen(buf));
127. digest_len = PS(hash_ops)->digest_size;
128. break;
129. #endif /* HAVE_HASH_EXT */
130.
131. //如果没有散列函数,则报错,还是E_ERROR级别的
132. default:
133. php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
134. efree(buf);
135. return NULL;
136. }
137.
138. //释放buf~
139. //内容已经去我们的hash_context里,比如md5_context、sha1_context。。。。。。
140. efree(buf);
141.
142. /*
143. session.entropy_file 给出了一个到外部资源(文件)的路径,
144. 该资源将在会话 ID 创建进程中被用作附加的熵值资源。
145. 例如在许多 Unix 系统下都可以用 /dev/random 或 /dev/urandom。
146. session.entropy_length 指定了从上面的文件中读取的字节数。默认为 0(禁用)。
147.
148. 如果entropy_length这个配置大于0,则:
149. */
150. if (PS(entropy_length) > 0)
151. {
152. #ifdef PHP_WIN32
153. unsigned char rbuf[2048];
154. size_t toread = PS(entropy_length);
155. if (php_win32_get_random_bytes(rbuf, (size_t) toread) == SUCCESS)
156. {
157. switch (PS(hash_func))
158. {
159. case PS_HASH_FUNC_MD5:
160. PHP_MD5Update(&md5_context, rbuf, toread);
161. break;
162. case PS_HASH_FUNC_SHA1:
163. PHP_SHA1Update(&sha1_context, rbuf, toread);
164. break;
165. # if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
166. case PS_HASH_FUNC_OTHER:
167. PS(hash_ops)->hash_update(hash_context, rbuf, toread);
168. break;
169. # endif /* HAVE_HASH_EXT */
170. }
171. }
172. #else
173. int fd;
174. fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
175. if (fd >= 0)
176. {
177. unsigned char rbuf[2048];
178. int n;
179. int to_read = PS(entropy_length);
180. while (to_read > 0) {
181. n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
182. if (n hash_update(hash_context, rbuf, n);
183. break;
184. #endif /* HAVE_HASH_EXT */
185. }
186. to_read -= n;
187. }
188. close(fd);
189. }
190. //结束entropy_length>0时的逻辑
191. #endif
192. }
193.
194. //还是散列计算的一部分,看来我们的hash_final(digest, hash_context);
195. efree(hash_context);
196. break;
197. #endif /* HAVE_HASH_EXT */
198. }
199.
200. /*
201. session.hash_bits_per_character允许用户定义将二进制散列数据转换为可读的格式时每个字符存放多少个比特。
202. 可能值为 '4'(0-9,a-f),'5'(0-9,a-v),以及 '6'(0-9,a-z,A-Z,"-",",")。
203. */
204. if (PS(hash_bits_per_character) < 4
205. || PS(hash_bits_per_character) > 6) {
206. PS(hash_bits_per_character) = 4;
207. php_error_docref(
208. NULL TSRMLS_CC,
209. E_WARNING,
210. "The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now"
211. );
212. }
213.
214. //将散列后的二进制数据digest用字符串表示成可读的形式,并放置在outid字符串里
215. outid = emalloc((size_t)((digest_len + 2) * ((8.0f / PS(hash_bits_per_character)) + 0.5)));
216. j = (int) (bin_to_readable((char *)digest, digest_len, outid, (char)PS(hash_bits_per_character)) - outid);
217. efree(digest);
218. if (newlen) {
219. *newlen = j;
220. }
221.
222. //返回outid
223. return outid;
224. }
根据以上代码可以得出结论,PHP的默认session_id生成算法还是比较随机的,除非攻击者能够同时猜中时间戳、毫秒数、后面的那个随机数。否则,安全系数还是很高的。