买房和租房,水木周平告诉你区别在那里,聪明人怎么选

中国99%的白领以及他们的家庭即将面临破产。而且是必然破产!无路可逃!这件事可能是发生在未来2-10年。你可以尽量去怀疑这个数字。但它必然发生,绝非危言耸听。’ 就如我预言中国国营企业职工必然失业一样,在当时来说没人信。但确实会发生。因为在中国这样一个发展中国家,其必然以不断的以通货膨胀和改革手段来换取经济的发展。而每一次改革所带来的阵痛都是由百姓来承担的。无论是上山下乡时迷茫的知青们还是改革开放带来的大量国企纷纷倒闭时大量下岗职工。如果他们能有一定的前瞻性的话,那么我想他们也许会为自己留一点后路。但是由于过分相信文件以及过分相信生活不会突变,所以才导致了他们的人生悲剧。

  有人说政府不会坐视不理白领破产,其实政府当然不愿意。可有的事情……怎么说呢。想想当年的下岗职工吧。引起了那么大的社会震动。又能怎样呢?今天的白领明天破产的时候也那样而已。
  而改革开放20多年来,中国的经济发展快速腾飞。但旧的体制并没有完全更改。各种重要行业依然施行的是政府垄断机制在运转。如:银行,金融,冶金,能源,信息,运输,医疗,教育,土地。在改革开放初期我国不可能对这些东西进行全方面的改革开放。但到了今天,垄断经营所带来的矛盾日益突出。
  首当其冲的就是房地产。由于我国的法制不健全,尤其是金融以及改革领域里出现了各种失误导致房地产节节攀升。随着房地产的增加以及外来人口向大城市集中。所以城市新民工也就是所谓的’白领’收入表面上也在提升,以北京为例3000-15000元人民币的月薪处处可见。但这一部分收入主要用于支付租房或还贷。
  为了深入地了解为什么99%的白领家庭会破产,我们就必须先了解房价为什么会这么高?高在哪里?资金是运转的?(这里的白领指买房或者准备买房族。)以及发展趋势带来的相关效应。本文会分三个阶段向你阐述。

 

1:导致房价爆涨的第一个因素:银行竞争下的贷款开放。
其实房价的爆涨的因素主要是因为百姓在替政府所犯下的错误买单。比如以前一个开发商通过某银行贷款了1000万开发了一个房子。如果还不起那他就倒霉了,因为所有的银行都是一个体系,你欠了钱没还就再也没有翻身的机会。但是中国在银行改革的基础上开了一条口子,为了各银行之间的竞争所以把工行,农行,建设银行等等全部独立运营。这本来是好事。但问题是这些银行都不是私有的,而是国家的。我们不难想象。当一个开发商从工行贷款1000万的后果。他只需要用500万来开发房子,然后把售价提升,再把这个开发中的房子按他的售价标准抵押从农行再贷款 2000万,然后再用这 2000万中的1000万开发一套售价更高的房子来找建设银行抵押贷款4000万。就是这样一个滚雪球的疯狂贷款模式。
房子卖不卖得出去不重要,关键是房价要高,不得降价。反正银行的钱不是私人的,所以稍微疏通一下行长加之又有’合法的’高零售价的楼盘做抵押所以自然就越来越好从银行贷款。
那么这样造成的结果就是,房子价格只攀不跌!因为不能跌!一跌银行贷出去的款就再也回不来了。这可是政府的银行,政府的钱!所以为了堵住这个资金黑洞一些被收买的专家、媒介便开始疯狂制造舆论用各种舆论手段威逼利诱人买房子。
比如土地资源严重紧张,再不买房将来就只能住在郊区呀之类的。导致人们不得不去买房。其实住不了市中心这种情况这根本不可能发生,城市居民是一个新老替换的过程,要上班的住城里,老人退休喜欢住郊区。只要人口不爆炸就不会出现上班族住不了市中心的情况。虽然这些舆论造成了很多人买房子,但是仅仅是这样,房价还没有高到现在这样的离谱。紧接着政府又犯了第二个错误。

2:导致房价爆涨的第二个错误:中国特色的按揭。
按揭本来是一种西方很流行的制度,也很合适。但这个制度一旦运用到中国就有点问题了。因为从大的体制上来说。所有银行都是国家的,而不是私人的。所以贷款这个关口就不可能控制得住。只要文件上说得过去,人们就能贷到款。
为了早日缓解第一个错误所带来的资金黑洞。政府开始实行个人按揭制度来售房。还经常举什么美国来太太和中国老太太的例子来诱惑人们去按揭。确实有人按揭了,而且是疯狂的按揭。只要和银行有点’路子’的人。他们先按揭一套80万的房子,自己出10万首付然后再从银行贷出70万。之后再把这个房子抬高价格到180万出售。这个时候他们的亲戚或者老爸老妈再去买下,也用按揭的方式自己出首付30万再从银行贷出150万。然后就不管了。他们不还贷款怎么办?银行爱收不收。反正根据合同我还不上钱你可以收走房子,我们两不相欠。
所以转了一圈,抬高了几倍价格的房子又回到了政府回到了银行手里。这就解释了为什么很多新楼盘刚开始修就被’炒房团’买走了。他们炒的不是赌房子会升值。而是拿了房子去收拾银行。
银行拿到这个房子怎么办?更不敢降价了。只好再加点价接着卖。所以普通老百姓现在根本别想买到真正合理价格的房子!即使你直接从开发商手里买来的房子都说不定已经转了好几次手又回到银行以及开发商手里的了。说句不好听的现在8000/平的房子里,有只有2500才是房价,有5500都是以为决策错误带来的资金黑洞!也就是说你正在替人任劳任怨地擦屁股。

3:第三个问题:白领家庭何时破产??
那么我们研究了房子价格为什么会涨,再来研究一下中国城市所谓的白领家庭破产的必然性。
首先国外白领收入确实是高,但是高得有价值。而中国所谓的白领则普遍素质较差。中国企业内耗严重。人人相轻,人人顽固。所以难怪外资企业一进入中国大陆市场就开始惊呼:’在中国办企业招不到人!’对此我也深有体会。那有人会说:’既然现在的白领不值这个身价,那水木周平你说说为什么他们还能拿到这样的薪水呢?’ 其实,这由于房地产的火爆造成的一个量子效应。银行损失的资金大量的经过少数人之手流向了市场。这些人开始在中国的其他领域疯狂投资。因为他们自己也知道房地产就快要崩盘了。他们这样一轮又一轮的投资热潮正在快速消化这些资金,他们投资互联网,投资高新技术,投资娱乐,投资很多很多。但起码付出的工资要够员工付房子月租或者月供吧。所以正是因为房价的高涨所以造就了中国城市人口工资水平的相对提升。不相信你自己算算你所在的城市白领阶级平均收入一旦交完每月的房钱,手上还能剩多少钱?我想这个问题就不用我再罗嗦了吧。大家心理有数。所以我可以说一旦房地产崩盘紧接着崩溃的就是你的工资。
有很多很多我认识的白领们都购了房。他们的算盘很简单:’两口子除开各种税收保险每月纯收入还 12000。交3000房钱算什么?我还能再买一套呢!’是的不算什么。但因为房子贵所以什么东西都贵。吃的贵,交通贵,学费贵,医疗费用更贵!!!所以交了房钱你再除开生活费用就基本上一分钱存不下来,就算存点也赶不上正常的通货膨胀率。问题是如果能一直保持这个现状的话,理论上说应该没事。你这二十年赚来的钱正好可以弥补政府的两个错误带来的亏损。
但问题也出在这里。随着WTO5年缓冲期的结束,大量外资通讯,银行,医疗,保险等等公司都会陆续进入中国。到时候没有人能阻止你把钱存入花旗,存入汇丰。请问一下到那个时候谁愿意把钱存在呆帐坏帐如此之多的中国国有银行呢?即使政府再怎么采取措施也可能挤兑,所以到时候会发生什么现在还很难说。但有一点可以肯定的是到时中国国有这些银行的压力将变得非常巨大。贷款就会难上加上,因为银行根本无钱可贷!同时大量具备高素质人材的外资企业进入中国必定带来市场的强烈冲击和大量现有企业的倒闭以及白领失业。也就是说。一旦外资企业加入竞争,中国现有的 99%的白领都将面临大环境下的就业压力!
而且外资银行一旦积累了资金开始投资房地产,那么由于它们是正常的操作流程所以造出来的房子就会便宜,其必然拉动全国房地产大幅下跌。如我刚才所说,房价一跌,紧跟着跌的就是你所在的企业的工资收入!可你之前买的房子还贷价格并不会降低或者减少,所以你将无力支付高昂的贷款。那么你的的房子会被银行收走,你的存款会被直接冻结。所以未来中国城市中的白领们最大的可能是和几十年前的中国国有企业职工一样。辛辛苦苦二十年,到头来竹篮打水一场空!

如何避免破产?
看到这里您应该明白,不要买房是一个避免破产的好办法。不过我还要提醒你,为了托住楼市不跌,他们还有个办法,那就是鼓吹老百姓不买房就不是个爷们儿!您别说,这还真有点效果。现在的人一张口第一句就是:’你有房吗。’似乎你没房就是个太监一样。我实在是气得连骂人的力气都没有了。还有人在百度水木周平这个帖吧里发帖说:’不买房子你住哪里?’我就奇怪了,住和买有必然联系吗?在中国一个土地都不属于你的房子卖给你和租给你有什么区别?(笑)。更别提土匪一样的物业和把人不当人的强制拆迁!这不纯粹是’皇帝的新装’吗?不过既然WTO中已经说明出版业和传媒业中国还是不对外开放的。那么舆论救市就会成为政府和开发商手中的最后一张王牌。
所以我们在面对很多花言巧语的时候还是自己多动动脑子。以后我们听到的房产的鬼话会越来越多,越来越令人发指!比如最近就有砖家在鼓吹房价不贵时都说: ‘什么即使年薪5万,两口子也是一年10万,5年就50万。所以房价当然不贵。’我奇怪的是居然有人点头称是?也许对于这种或者此类已经进化到了不吃不喝不病不穿不动且爹娘早已死绝不用赡养的砖家来说也许还真是那么回事。所以大家注意提高警惕。
结束语:
已经买房或者准备买房的白领一族必定随着房价的崩溃而崩溃,那会是一个缓慢发生的过程。短则两年,长则十年。但这是不可逆转的趋势。所以中国99%城市白领一族已经面临破产一说绝非危言耸听!今天你往银行交的每一分房钱都是替政府替炒房者补洞,只有一小部分是真正的房钱。明天大环境一变,你没有那么多资金来补洞的时候就会被市场和银行一脚踢回老家,换一批新人来接着补。不信?走着瞧呗!–PS:为什么我说99%这个数,是因为根据我的了解99%的人一旦月薪过5000就开始买房,甚至3000,4000都买。小俩口什么都不明白这样买下去人生一定会很惨。我只是替他们感到忧伤。当然如果你是那1%的智者,多劝救他们吧。独乐乐不如众乐乐。

Advertisements

PHP: 深入pack/unpack

PHP作为一门为web而生的服务器端开发语言,被越来越多的公司所采用。其中不乏大公司,如腾迅、盛大、淘米、新浪等。在对性能要求比较高的项目中,PHP也逐渐演变成一门前端语言,用于访问后端接口。或者不同项目之间需要共享数据的时候,通常可以抽取出数据层,通过PHP来访问。

写在前面的话

本文介绍的是通过二进制数据包的方式通信,演示语言为PHP和Golang。PHP提供了pack/unpack函数来进行二进制打包和二进制解包。在具体讲解之前,我们先来了解一些基础知识。

什么是字节序

在不同的计算机体系结构中,对于数据(比特、字节、字)等的存储和传输机制有所不同,因而引发了计算机领域中一个潜在但是又很重要的问题,即通信双方交流的信息单元应该以什么样的顺序进行传送。如果达不成一致的规则,计算机的通信与存储将会无法进行。目前在各种体系的计算机中通常采用的字节存储机制主要有两种:大端(Big-endian)和小端(Little-endian)。这里所说的大端和小端即是字节序。

MSB和LSB

  • MSB是Most Significant Bit/Byte的首字母缩写,通常译为最重要的位或最重要的字节。它通常用来表示在一个bit序列(如一个byte是8个bit组成的一个序列)或一个byte序列(如word是两个byte组成的一个序列)中对整个序列取值影响最大的那个bit/byte。

  • LSB是Least Significant Bit/Byte的首字母缩写,通常译为最不重要的位或最不重要的字节。它通常用来表明在一个bit序列(如一个byte是8个bit组成的一个序列)或一个byte序列(如word是两个byte组成的一个序列)中对整个序列取值影响最小的那个bit/byte。

  • 对于一个十六进制int类型整数0x12345678来说,0x12就是MSB,0x78就是LSB。而对于0x78这个字节而言,它的二进制是01111000,那么最左边的那个0就是MSB,最右边的那个0就是LSB。

大端序

  • 大端序又叫网络字节序。大端序规定高位字节在存储时放在低地址上,在传输时高位字节放在流的开始;低位字节在存储时放在高地址上,在传输时低位字节放在流的末尾。

小端序

  • 小端序规定高位字节在存储时放在高地址上,在传输时高位字节放在流的末尾;低位字节在存储时放在低地址上,在传输时低位字节放在流的开始。

网络字节序

  • 网络字节序是指大端序。TCP/IP都是采用网络字节序的方式,java也是使用大端序方式存储。

主机字节序

  • 主机字节序代表本机的字节序。一般是小端序,但也有一些是大端序。

  • 主机字节序用在协议描述中则是指小端序。

总结

  • 字节序只针对于多字节类型的数据。比如对于int类型整数0x12345678,它占有4个字节的存储空间,存储方式有大端(0x12, 0x34, 0x56, 0x78)和小端(0x78, 0x56, 0x34, 0x12)两种。可以看到,在大端或小端的存储方式中,是以字节为单位的。所以对于单字节类型的数据,不存在字节序这个说法。

pack/unpack详解

PHP pack函数用于将其它进制的数字压缩到位字符串之中。也就是把其它进制数字转化为ASCII码字符串。

格式字符翻译

  • a — 将字符串空白以 NULL 字符填满

  • A — 将字符串空白以 SPACE 字符 (空格) 填满

  • h — 16进制字符串,低位在前以半字节为单位

  • H — 16进制字符串,高位在前以半字节为单位

  • c — 有符号字符

  • C — 无符号字符

  • s — 有符号短整数 (16位,主机字节序)

  • S — 无符号短整数 (16位,主机字节序)

  • n — 无符号短整数 (16位, 大端字节序)

  • v — 无符号短整数 (16位, 小端字节序)

  • i — 有符号整数 (依赖机器大小及字节序)

  • I — 无符号整数 (依赖机器大小及字节序)

  • l — 有符号长整数 (32位,主机字节序)

  • L — 无符号长整数 (32位,主机字节序)

  • N — 无符号长整数 (32位, 大端字节序)

  • V — 无符号长整数 (32位, 小端字节序)

  • f — 单精度浮点数 (依计算机的范围)

  • d — 双精度浮点数 (依计算机的范围)

  • x — 空字节

  • X — 倒回一位

  • @ — 填入 NULL 字符到绝对位置

格式字符详解

  • pack/unpack允许使用修饰符*和数字,紧跟在格式字符之后,用于指定该格式的个数;

  • a和A都是用来打包字符串的,它们的唯一区别就是当小于定长时的填充方式。a以NULL填充,NULL事实上是”的表示,代表空字节,8个位上全是0。A以空格填充,空格也即ASCII码为32的字符。这里有一个关于填充的使用场景的例子:请求登录的数据包规定用户名不超过20个字节,密码经过md5加密后是固定的32个字节。用户名就是变长的,为了便于服务器端读取和处理,通常会填充成定长。当然,这只是使用的方式之一,事实上还可以用变长的方式传递数据包,但这不在本文的探讨范围内。字符串有一点麻烦的是编码问题,尤其是在跟不同的平台通信时更为突出。比如在用pack进行打包字符串时,事实上是将字符内部的编码打包进去。单字节字符就没有问题,因为单字节在所有平台上都是一致的。来看个例子(pack.php):

1 <?php
2 $bin = pack("a""d");
3 echo "output: " $bin "\n";
4 echo "output: 0x" . bin2hex($bin) . "\n";
1 $ php -f pack.php
2 output: d
3 output: 0x64

$bin是返回的二进制字符,您可以直接输出它,PHP知道如何处理。通过bin2hex方法将$bin转换成十六进制可以知道,十六进制0x64表示的是字符d。对于中文字符(多字节字符)来说,通常有GBK编码、BIG5编码以及UTF8编码等。比如在GBK编码中,一个中文字符采用2个字节来表示;在UTF8编码中,一个中文字符采用3个字节来表示。这通常需要协商采用统一的编码,否则会由于内部的表示不一致导致无法处理。在PHP中只要将文件保存为特定的编码格即可,其它语言可能跟操作系统相关,因此或许需要编码转换。本文的例子一概基于UTF8编码。继续来看个例子:

1 <?php
2 $bin = pack("a3""中");
3 echo "output: 0x" . bin2hex($bin) . "\n";
4 echo "output: " chr(0xe4) . chr(0xb8) . chr(0xad) . "\n";
5 echo "output: " $bin{0} . $bin{1} . $bin{2} . "\n";
1 $ php -f pack.php
2 output: 0xe4b8ad
3 output: 中
4 output: 中

您可能会觉得很奇怪,后面2个输出是一样的。ASCII码表示单字节字符(其中包括英文字母、数字、英文标点符号、不可见字符以及控制字符等等),它总是小于0x80,即小于十进制的128。当在处理字符时,如果字节小于0x80,则把它当作单字节来处理,否则会继续读取下一个字节,这通常跟编码有关,GBK会将2个字节当成一个字符来处理,UTF8则需要3个字节。有时候在PHP中需要做类似的处理,比如计算字符串中字符的个数(字符串可能包含单字节和多字节),strlen方法只能计算字节数,而mb_strlen需要开启扩展。类似这样的需求,其实很容易处理:

01 <?php
02 function mbstrlen($str)
03 {
04     $len strlen($str);
05      
06     if ($len <= 0)
07     {
08         return 0;
09     }
10      
11     $count  = 0;
12      
13     for ($i = 0; $i $len$i++)
14     {
15         $count++;
16         if (ord($str{$i}) >= 0x80)
17         {
18             $i += 2;
19         }
20     }
21      
22     return $count;
23 }
24  
25 echo "output: " . mbstrlen("中国so强大!") . "\n";
1 $ php -f pack.php
2 output: 7

以上代码的实现就是利用单字节字符的ASCII码小于0x80。至于要跳过几个字节,这要看具体是什么编码。接下来通过例子来看看a和A的区别:

$GOPATH/src

—-pack_test

——–main.go

main.go的源码(只是用于测试,没有考虑细节):

01 package main
02  
03 import (
04     "fmt"
05     "net"
06 )
07  
08 const BUF_SIZE = 20
09  
10 func handleConnection(conn net.Conn) {
11     defer conn.Close()
12     buf := make([]byte, BUF_SIZE)
13     n, err := conn.Read(buf)
14      
15     if err != nil {
16         fmt.Printf("err: %v\n", err)
17         return
18     }
19  
20     fmt.Printf("\n已接收:%d个字节,数据是:'%s'\n", n, string(buf))
21 }
22  
23 func main() {
24     ln, err := net.Listen("tcp"":9872")
25      
26     if err != nil {
27         fmt.Printf("error: %v\n", err)
28         return
29     }
30  
31     for {
32         conn, err := ln.Accept()
33         if err != nil {
34             continue
35         }
36         go handleConnection(conn)
37     }
38 }

代码很简单,收到数据,然后输出。

pack.php

01 <?php
02 $host "127.0.0.1";
03 $port "9872";
04  
05 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
06   or die("Unable to create socket\n");
07  
08 @socket_connect($socket$host$portor die("Connect error.\n");
09  
10 if ($err = socket_last_error($socket))
11 {
12  
13   socket_close($socket);
14   die(socket_strerror($err) . "\n");
15 }
16  
17 $binarydata = pack("a20""中国强大");
18 $len = socket_write ($socket $binarydatastrlen($binarydata));
19 socket_close($socket);
1 cd $GOPATH/src/pack_test
2 $ go build
3 $ ./pack_test
1 $ php -f pack.php

当执行php后,可以看到服务器端在控制台输出:

1 已接收:20个字节,数据是:'中国强大'

以上的输出中,单引号不是数据的一部分,只是为了便于观察。很明显,我们打包的字符串只占12字节,a20表示20个a,您当然可以连续写20个a,但我想您不会这么傻。如果是a*的话,则表示任意多个a。通过服务器端的输出来看,PHP发送了20个字节过去,服务器端也接收了20个字节,但因为填充的是空字符,所以您不会看到有什么不一样的地方。现在我们将a20换成A20,代码如下:

01 <?php
02 $host "127.0.0.1";
03 $port "9872";
04  
05 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
06   or die("Unable to create socket\n");
07  
08 @socket_connect($socket$host$portor die("Connect error.\n");
09  
10 if ($err = socket_last_error($socket))
11 {
12  
13   socket_close($socket);
14   die(socket_strerror($err) . "\n");
15 }
16  
17 $binarydata = pack("A20""中国强大");
18 $len = socket_write ($socket $binarydatastrlen($binarydata));
19 socket_close($socket);
1 $ php -f pack.php

您会发现服务器端的输出不一样了:

1 已接收:20个字节,数据是:'中国强大        '

是的,空格存在于数据中。这就是a和A的区别。

  • h和H的描述看起来有些奇怪。它们都是读取十进制,以十六进制方式读取,以半字节(4位)为单位。这听起来有些拗口,还是以实例来说明:

1 <?php
2 echo "output: " . pack("H", 0x5) . "\n";
1 $ php -f pack.php
2 output: P

首先是读取十进制,所以0x5会转成十进制的5,然后以半字节为单位并且以十六进制方式读取,为了补足8位,所以需要在5后面补0,变成0x50。别忘了十六进制的一位相当于二进制的四位。0x50正好是字符P的ASCII码。

1 <?php
2 echo "output: " chr(0x50) . "\n";
1 $ php -f pack.php
2 output: P

h和H的差别在于h是低位在前,H是高位在前,拿前面的例子来看看h的行为:

1 <?php
2 $bin = pack("h", 0x5);
3 echo "output: " $bin "\n";
4 echo "output: " . ord($bin) . "\n";
1 $ php -f pack.php
2 output: 
3 output: 5

读取十进制的5,后面补0,变成十六进制的0x50,因为H是高位在前,所以没有变化,而h就需要将0x50变成0x05。由于0x05是不可见字符,所以上面的字符输出是空的。

h和H是以半字节为单位,h2和H2则表示一次读取8位,同理h3和H3可以推导出来,但是别忘了补足8位哦!

1 <?php
2 echo "output: " . pack("H", 0x47) . "\n";
1 $ php -f pack.php
2 output: p

以上的代码中,0x47为十进制的71,因为读取半个字节,所以变成0x7,后面补0变成0x70,则刚好是字符p的ASCII码。如果换成是h格式化,则最终的结果是0x07,因为低位在前。

对于一次读取多个字节,也以同样的规则:

1 <?php
2 echo "output: " . pack("H2h2", 0x47, 0x56) . "\n";
1 $ php -f pack.php
2 output: qh

0x47是十进制的71,由于使用H2格式化,所以一次读取8位,最后变成十六进制的0x71,即字符q的ASCII码。0x56是十进制的86,由于使用h2格式化,所以一次读取8位,最后变成十六进制的0x86,但是由于h表示低位在前,因此0x86变成0x68,即字符h的ASCII码。

  • c和C都表示字符,前者表示有符号字符,后者表示无符号字符。

1 <?php
2 echo "output: " . pack("c", 65) . "\n";
3 echo "output: " . pack("C", 65) . "\n";
1 $ php -f pack.php
2 output: A
3 output: A
  • s为有符号短整数;S为无符号短整数。它们都为主机字节序,并且为16位。通常为主机字节序的格式化字符,一般只用于单机的操作,因为您无法确定主机字节序究竟是大端还是小端。当然,您一定要这么干的话,也是有办法来获取本机字节序是属于大端或小端,但那样是没有必要的。稍后就会给出一个通过PHP来判断字节序的例子。

1 <?php
2 $bin1 = pack("s", 345);
3 $bin2 = pack("S", 452);
4 print_r(unpack("sshort1"$bin1));
5 print_r(unpack("sshort2"$bin2));
1 $ php -f pack.php
2 Array
3 (
4     [short1] => 345
5 )
6 Array
7 (
8     [short2] => 452
9 )
  • n和v除了明确指定了字节序,其它行为跟s和S是一样的。

  • i和I依赖于机器大小及字节序,很少用它们。

  • l、L、N、V跟s、S、n、v类似,除了表示的大小不同,前者都为32位,后者都为16位。

  • f、d是因为float和double与CPU无关。一般来说,编译器是按照IEEE标准解释的,即把float/double看作4/8个字符的数组进行解释。因此,只要编译器是支持IEEE浮点标准的,就不需要考虑字节顺序。

  • 剩下的x、X和@用得比较少,对此不作深究。

unpack的用法

  • unpack是用来解包经过pack打包的数据包,如果成功,则返回数组。其中格式化字符和执行pack时一一对应,但是需要额外的指定一个key,用作返回数组的key。多个字段用/分隔。例如:

1 <?php
2 $bin = @pack("a9SS""陈一回", 20, 1);
3 $data = @unpack("a9name/sage/Sgender"$bin);
4  
5 if (is_array($data))
6 {
7     print_r($data);
8 }
1 $ php  -f pack.php
2 Array
3 (
4     [name] => 陈一回
5     [age] => 20
6     [gender] => 1
7 )

一些例子

  • 判断大小端

01 <?php
02 function IsBigEndian()
03 {
04     $bin = pack("L", 0x12345678);
05     $hex = bin2hex($bin);
06     if (ord(pack("H2"$hex)) === 0x78)
07     {
08         return FALSE;
09     }
10  
11     return TRUE;
12 }
13  
14 if (IsBigEndian())
15 {
16     echo "大端序";
17 }
18 else
19 {
20     echo "小端序";
21 }
22  
23 echo "\n";
1 $ php -f pack.php
2 小端序
  • 网络通信

    比如现在要通过PHP发送数据包到服务器来登录。在仅需要提供用户名(最多30个字节)和密码(md5之后固定为32字节)的情况下,可以构造如下数据包(当然这事先需要跟服务器协商好数据包的规范,本例以网络字节序通信):

    包结构:

字段 字节数 说明
包头 定长 每一个通信消息必须包含的内容
包体 不定长 根据每个通信消息的不同产生变化

其中包头详细内容如下:

字段 字节数 类型 说明
pkg_len 2 ushort 整个包的长度,不超过4K
version 1 uchar 通讯协议版本号
command_id 2 ushort 消息命令ID
result 2 short 请求时不起作用;请求返回时使用

当然实际中可能会涉及到各种校验。本文为了简单,只是列举一下通常的工作流程及处理的方式。

登录(执行命储1001)

字段 字节数 类型 说明
用户名 30 uchar[30] 登录用户名
密码 32 uchar[32] 登录密码

包头是定长的,通过计算可知包头占7个字节,并且包头在包体之前。比如用户陈一回需要登录,密码是123456,则代码如下:

01 <?php
02 $version    = 1;
03 $result     = 0;
04 $command_id = 1001;
05 $username   "陈一回";
06 $password   = md5("123456");
07 // 构造包体
08 $bin_body   = pack("a30a32"$username$password);
09 // 包体长度
10 $body_len   strlen($bin_body);
11 $bin_head   = pack("nCns"$body_len$version$command_id$result);
12 $bin_data   $bin_head $bin_body;
13 // 发送数据
14 // socket_write($socket, $bin_data, strlen($bin_data));
15 // socket_close($socket);

服务器端通过读取定长包头,拿到包体长度,再读取并解析包体。大致的过程就是这样。当然服务器端也会返回响应包,客户端做相应的读取处理。

一个很不错的适合PHPER们书单,推荐给大家

推荐一些不错的计算机书籍。
# PHP
《PHP程序设计》(第2版)  –PHP语法和入门最好的书
《PHP5权威编程》  –PHP入门后升级书
《深入PHP:面向对象、模式与实践》(第3版) –理解PHP中的面向对象和设计模式
《高性能PHP应用开发》 –了解一些基本简单的PHP优化
《PHP核心技术与最佳实践》 –了解很多PHP高级技术和延伸技术
《Extending and Embedding PHP》–PHP内核介绍和扩展开发最好的书!没有之一!
# MySQL
《MySQL必知必会》  –极好的MySQL语法参考书
《MySQL 5 权威指南》(第3版) –MySQL综合全面使用书籍,适合入门
《深入浅出MySQL——数据库开发、优化与管理维护》 –很多实用的MySQL技巧
《MySQL性能调优与架构设计》 –关于很多架构和优化配置
《高可用MySQL:构建健壮的数据中心》 –DBA和架构理解有兴趣可以读
《高性能MySQL》(第2版)  –适合DBA和开发的经典书籍!推荐!
《深入理解MySQL核心技术》  –初窥MySQL内部工作原理
《MySQL技术内幕:InnoDB存储引擎》 –目前深入分析InnoDB引擎最好的书

# Linux 管理:
《Linux 系统管理技术手册》 案头必备的工具书。
《鸟哥的 Linux 私房菜》不错的入门书。
《Linux 101 Hacks》常用命令手册
《UNIX Shell Scripting》写脚本的参考书
《The Linux Command Line》更详细的命令手册
# Linux 编程:
《Linux 系统编程》对常用 API 讲述最详细的一本书
《UNIX 环境高级编程》经典
《The Linux Programming Interface》与上本书配套
《程序员的自我修养》别被名字误导,极好的一本深度基础书。
《深入理解 Linux 内核》可以翻翻,对提升细节理解有好处。
《UNIX 网络编程》经典
《TCP/IP协议详细》第一卷 –经典的无以复加
《TCP/IP 高级编程》好书
# C/C++:
《C 程序设计语言》入门书
《Lnux C 编程一站式学习》Linux 下开发的入门书
《C 语言核心技术》参考手册
《彻底搞定 C 指针》最好的指针入门书
《C++ 编程思想》经典
《高质量程序设计指南——C/C++语言》经典
《C 专家编程》
《C 和指针》
《C 陷阱与缺陷》
# Golang:
《Learing Go》简单
《The Go Programming Language》比较详细
《The way to Go》提升
# Javascript:
《Javascript, A Beginner’s Guide》
《Object-Oriented Javascript》
# Python:
《Python Pocket Reference》适合经常翻翻
《Expert Python Programming》某些地方很有启发
# 其他:
《深入理解计算机系统》经典,必读
《计算机组成与设计》可以翻翻
《汇编语言》王爽  最好的汇编入门书
《数据结构》C 语言版  经典
《Java 数据结构和算法》更易阅读
《Debug Hacks 中文版》GDB 入门书
《设计模式——可复用面向对象软件的基础》经典
《MongoDB, The Definitive Guide》
《算法导论》第三版 –经典书籍
《数据库系统实现》(第2版)–想自己开发数据库可以看看
《精通正则表达式(第3版)》 –深入了解和使用正则

phper应该懂得的css中高级知识

1.对WEB标准以及W3C的理解与认识
l  标签闭合、标签小写、不乱嵌套、提高搜索机器人搜索几率;
l  使用外链css和js脚本、结构行为表现的分离、文件下载与页面速度更快;
l  内容能被更多的用户所访问、内容能被更广泛的设备所访问、更少的代码和组件;
l  容易维护、改版方便,不需要变动页面内容;
l  提供打印版本而不需要复制内容、提高网站易用性;
2.xhtml和html有什么区别
HTML是一种基本的WEB网页设计语言,XHTML是一个基于XML的置标语言
最主要的不同:
l  XHTML 元素必须被正确地嵌套。
l  XHTML 元素必须被关闭。
l  标签名必须用小写字母。
l  XHTML 文档必须拥有根元素。
3.Doctype? 严格模式与混杂模式-如何触发这两种模式,区分它们有何意义?
用于声明文档使用那种规范(html/Xhtml)一般为 严格 过度 基于框架的html文档
加入XMl声明可触发,解析方式更改为IE5.5拥有IE5.5的bug
4.行内元素有哪些?块级元素有哪些?CSS的盒模型?
块级元素:div p h1 h2 h3 h4 form ul
行内元素: a b br i span input select
Css盒模型:内容,border,margin,padding
5.CSS引入的方式有哪些? link和@import的区别是?
内联 内嵌 外链 导入
区别 :同时加载;前者无兼容性,后者CSS2.1以下浏览器不支持;Link支持使用javascript改变样式,后者不可
6.CSS选择符有哪些?哪些属性可以继承?优先级算法如何计算?内联和important哪个优先级高?
l  标签选择符 类选择符 id选择符
l  继承不如指定Id>class>标签选择l  内联和important优先级高是后者优先级高
7.前端页面有哪三层构成,分别是什么?作用是什么?
结构层 Html 表示层 CSS 行为层 js
8.css的基本语句构成是?
选择器{属性1:值1;属性2:值2;……}
9.你做的页面在哪些流览器测试过?这些浏览器的内核分别是什么?
Ie(Ie内核) 火狐(Gecko) 谷歌(webkit)opear(Presto)
10.写出几种IE6 BUG的解决方法
n  双边距BUG float引起的 使用display
n  像素问题 使用float引起的 使用dislpay:inline -3px
n  超链接hover 点击后失效 使用正确的书写顺序 linkvisited hover activen  Ie z-index问题 给父级添加position:relative
n  Png 透明 使用js代码改
n  Min-height 最小高度 !Important 解决’
n  select 在ie6下遮盖 使用iframe嵌套
n  为什么没有办法定义1px左右的宽度容器(IE6默认的行高造成的,使用over:hidden,zoom:0.08line-height:1px)
11.<img>标签上title与alt属性的区别是什么?
l  Alt 当图片不显示是用文字代表。l  Title 为该属性提供信息
12.描述css reset的作用和用途。
Reset重置浏览器的css默认属性浏览器的品种不同,样式不同,然后重置,让他们统一
13.解释css sprites,如何使用。
Css 精灵 把一堆小的图片整合到一张大的图片上,减轻服务器对图片的请求数量
14.浏览器标准模式和怪异模式之间的区别是什么?
l  盒子模型 渲染模式的不同
l  使用window.top.document.compatMode 可显示为什么模式
15.你如何对网站的文件和资源进行优化?期待的解决方案包括:
l  文件合并
l  文件最小化/文件压缩
l  使用CDN托管
l  缓存的使用
16.什么是语义化的HTML?
直观的认识标签 对于搜索引擎的抓取有好处
17.清除浮动的几种方式,各自的优缺点
  使用空标签清除浮动clear:both(理论上能清楚任何标签,,,增加无意义的标签)
  使用overflow:auto(空标签元素清除浮动而不得不增加无意代码的弊端,使用zoom:1用于兼容IE)
  是用afert伪元素清除浮动(用于非IE浏览器)

正则表达式后向引用

一直没有弄明白逆向引用(也译做间接引用或后向引用)到底是什么概念,也一直不知道\\1到底怎么就能引用到前面的内容,经过看教程,明白这个是和子模式联系在一起的。

正则表达式一个最重要的特性就是将匹配成功的模式的某部分进行存储供以后使用这一能力。
对一个正则表达式模式或部分模式两边添加圆括号()可以把这部分表达式存储到一个临时缓冲区中。
所捕获的每个子匹配都按照在正则表达式模式中从左至右所遇到的内容按顺序存储。
存储子匹配的缓冲区编号从1开始,连续编号至最大99个子表达式。
每个缓冲区都可以使用’\n'(或用’$n’)访问,其中n为1至99的阿拉伯数字,用来按顺序标识特定缓冲区(子表达式)。
例1:最简单最有用的例子是确定文字中连续出现两个相同单词的位置

复制代码代码如下:

<?php
$string = “Is is the cost of of gasoline going up up”;
$pattern = “/\b([a-z]+) \\1\b/i”; //这里的\\1不能使用\$1或$1
$str = preg_replace($pattern, “\\1”, $string); //这里的\\1可以使用\$1或$1,引用第一个子匹配
echo $str; //效果是Is the cost of gasoline going up
?>

例中的子表达式就是圆括号内的项。\b匹配单词的开始或结束。+匹配重复一次或更多次。
该子表达式匹配的是一个或多个字母字符的单词,即由'[a-z]+’匹配的。
该正则表达式的第二部分是对前面所捕获的子匹配的引用,也就是由附加表达式所匹配的第二次出现的单词,用’\\1’来引用第一个子匹配,第一个\是转义符。
i是正则表达式中的修正符。i:忽略大小写。
例2:
正则表达式的逆向引用($0-99或\-99)和子模式以(/()/)开始。
这里$0是全部匹配模式的匹配项。$1是第1个子匹配,$2至$99依次是第2个至第99个子匹配。
用$1-99后向引用子匹配时,如果后面的字符是数字,要用花括号区别开。例:${1}1 。
函数
mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
功能
在 subject 中搜索 pattern 模式的匹配项并替换为 replacement。如果指定了 limit,则仅替换 limit 个匹配,如果省略 limit 或者其值为 -1,则所有的匹配项都会被替换。
replacement可以包含\\n形式或$n形式的逆向引用,n可以为0到99,\\n表示匹配pattern第n个子模式的文本,\表示匹配整个pattern的文本。
子模式
$pattern参数中被圆括号括起来的正则表达式,子模式的数目即从左到右圆括号的数目。(pattern即模式)
例子

复制代码代码如下:

<?php
$time=date(“Y-m-d H:i:s”);
$pattern = “/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/i”;
$replacement = “\$time格式为:$0<BR>替换后的格式为:$1年$2月$3日 $4时$5分$6秒”;
print preg_replace($pattern, $replacement, $time);
?>

输出:
$time格式为:2011-07-25 17:59:26
替换后的格式为:2011年07月25日 17时59分26秒
附正则表达式符号对照表

字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\\’ 匹配 “\” 而 “\(” 则匹配 “(“。
^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。
? 当 该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。
. 匹配除 “\n” 之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用象 ‘[.\n]’ 的模式。
(pattern) 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 ‘\(‘ 或 ‘\)’。
(?:pattern) 匹 配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。例如, ‘industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。
(?=pattern) 正 向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,’Windows (?=95|98|NT|2000)’ 能匹配 “Windows 2000” 中的 “Windows” ,但不能匹配 “Windows 3.1” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 负 向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如’Windows (?!95|98|NT|2000)’ 能匹配 “Windows 3.1” 中的 “Windows”,但不能匹配 “Windows 2000” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x|y 匹配 x 或 y。例如,’z|food’ 能匹配 “z” 或 “food”。'(z|f)ood’ 则匹配 “zood” 或 “food”。
[xyz] 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,'[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\cx 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。
\d 匹配一个数字字符。等价于 [0-9]。
\D 匹配一个非数字字符。等价于 [^0-9]。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
\w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]’。
\W 匹配任何非单词字符。等价于 ‘[^A-Za-z0-9_]’。
\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41′ 匹配 “A”。’\x041′ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。.
\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1′ 匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
\nm 标 识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。
\nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。

理解php Hash函数,增强密码安全

服务器和数据库的资料偶尔会被窃取,因此需要保证发生这种情况时一些重要的用户数据,比如密码,是别人无法获取的。这里我们将要讨论Hash的原理,以及它是如何保护Web应用程序中的密码安全的。

1.声明
密码学是一个复杂的话题,我也不是这方面的专家。许多高校和研究机构在这方面都有长期的研究。在这篇文章里,我希望尽量使用简单易懂的方式向你展示一种安全存储Web程序密码的方法。
2.“Hash”是做什么的?
“Hash将一段数据(小数据或大数据)转换成一段相对短小的数据,如字符串或整数。”
这是依靠单向hash函数来完成的。所谓单向是指很难(或者是实际上不可能)将其反转回来。一个常见的hash函数的例子是md5(),它流行于各种计算机语言和系统。

复制代码代码如下:

$data = “Hello World”;
$hash = md5($data);
echo $hash; // b10a8db164e0754105b7a99be72e3fe5

使用md5()运算出来的结果总是32个字符的字符串,不过它只包含16进制的字符,从技术上来说它也可以用128位(16字节)的整形数来表示。你可以使用md5()来处理很长的字符串和数据,但是你始终得到的是一个固定长度的hash值,这也可能可以帮助你理解为什么这个函数是“单向”的。
3.使用Hash函数来存储密码
典型的用户注册过程:
用户填写注册表单,其中包含密码字段;
程序将所有用户填写的信息存储到数据库中;
然而密码在存储到数据库前通过hash函数加密处理;
原始的密码不再存储在任何地方,或者说它被丢弃了。
用户登录过程:
用户输入用户名和密码;
程序将密码通过以注册相同的hash函数进行加密;
程序从数据库查到用户,并读取hash后的密码;
程序比较用户名和密码,如果匹配则给用户授权。
如何选择合适的方法来加密密码,我们将在文章的后面讨论这个问题。
4.问题1:hash碰撞
hash碰撞是指对两个不同的内容进行hash得到了相同的hash值。发生hash碰撞的可能性取决于所用的hash算法。
如何产生?
举个例子,一些老式程序使用crc32()来hash密码,这种算法产生一个32位的整数作为hash结果,这意味着只有2^32 (即4,294,967,296) 种可能的输出结果。
让我们来hash一个密码:

复制代码代码如下:

echo crc32(‘supersecretpassword’);
// outputs: 323322056

现在我们假设一个人窃取了数据库,得到了hash过的密码。他可能不能将323322056还原为‘supersecretpassword’,然而他可以找到另一个密码,也能被hash出同样的值。这只需要一个很简单的程序:

复制代码代码如下:

set_time_limit(0);
$i = 0;
while (true) {
if (crc32(base64_encode($i)) == 323322056) {
echo base64_encode($i);
exit;
}
$i++;
}

这个程序可能需要运行一段时间,但是最终它能返回一个字符串。我们可以使用这个字符串来代替‘supersecretpassword’,并使用它成功的登录使用该密码的用户帐户。
比如在我的电脑上运行上面的程序几个月后,我得到了一个字符串:‘MTIxMjY5MTAwNg==’。我们来测试一下:

复制代码代码如下:

echo crc32(‘supersecretpassword’);
// outputs: 323322056
echo crc32(‘MTIxMjY5MTAwNg==’);
// outputs: 323322056

如何解决?
现在一个稍强一点的家用PC机就可以一秒钟运行十亿次hash函数,所以我们需要一个能产生更大范围的结果的hash函数。比如md5()就更合适一些,它可以产生128位的hash值,也就是有340,282,366,920,938,463,463,374,607,431,768,211,456种可能的 输出。所以人们一般不可能做那么多次循环来找到hash碰撞。然而仍然有人找到方法来做这件事情,详细可以查看例子。
sha1()是一个更好的替代方案,因为它产生长达160位的hash值。
5.问题2:彩虹表
即使我们解决了碰撞问题,还是不够安全。
“彩虹表通过计算常用的词及它们的组合的hash值建立起来的表。”
这个表可能存储了几百万甚至十亿条数据。现在存储已经非常的便宜,所以可以建立非常大的彩虹表。
现在我们假设一个人窃取了数据库,得到了几百万个hash过的密码。窃取者可以很容易地一个一个地在彩虹表中查找这些hash值,并得到原始密码。虽然不是所有的hash值都能在彩虹表中找到,但是肯定会有能找到的。
如何解决?
我们可以尝试给密码加点干扰,比如下面的例子:

复制代码代码如下:

$password = “easypassword”;
// this may be found in a rainbow table
// because the password contains 2 common words
echo sha1($password); // 6c94d3b42518febd4ad747801d50a8972022f956
// use bunch of random characters, and it can be longer than this
$salt = “f#@V)Hu^%Hgfds”;
// this will NOT be found in any pre-built rainbow table
echo sha1($salt . $password); // cd56a16759623378628c0d9336af69b74d9d71a5

在这里我们所做的只是在每个密码前附加上一个干扰字符串后进行hash,只要附加的字符串足够复杂,hash后的值肯定是在预建的彩虹表中找不到的。不过现在还是不够安全。
6.问题3:还是彩虹表
注意,彩虹表可能在窃取到干拢字符串后重头开始建立。干扰字符串一样也可能被和数据库一起被窃取,然后他们可以利用这个干扰字符串从头开始创建彩虹表,如“easypassword”的hash值可能在普通的彩虹表中存在,但是在新建的彩虹表里,“f#@V)Hu^%Hgfdseasypassword”的hash值也会存在。
如何解决?
我们可以对每个用户使用唯一的干扰字符串。一个可用的方案就是使用用户在数据库中的id:

复制代码代码如下:

$hash = sha1($user_id . $password);

这种方法的前提是用户的id是一个不变的值(一般应用都是这样的)
我们也可以为每个用户随机生成一串唯一的干扰字符串,不过我们也需要将这个串存储起来:

复制代码代码如下:

// generates a 22 character long random string
function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
$unique_salt = unique_salt();
$hash = sha1($unique_salt . $password);
// and save the $unique_salt with the user record
// …

这种方法就防止了我们受到彩虹表的危害,因为每一个密码都使用一个不同的字符串进行了干扰。攻击者需要创建和密码数量一样的彩虹表,这是很不切实际的。
7.问题4:hash速度
大部分hash算法在设计时就考虑了速度问题,因为它一般用来计算大数据或文件的hash值,以验证数据的正确性和完整性。
如何产生?
如前所述,现在一台强劲的PC机可以一秒运算数十亿次,很容易用暴力破解法去尝试每个密码。你可能会以为8个以上字符的密码就可以避免被暴力破解了,但是让我们来看看是否真是这样:
如果密码可以包含小写字母,大写字母和数字,那就有62(26+26+10)个字符可选;
一个8位的密码有62^8种可能组合,这个数字略大于218万亿。
以一秒钟运算10亿次hash值的速度计算,这只需要60小时就可以解决。
对于一个6位的密码,也是很常用的密码,只需要1分钟就可以破解。要求9到10位的密码可能会比较安全了,不过这样有的用户可能会觉得很麻烦。
如何解决?
使用慢一点的hash函数。
“假设你使用一个在相同硬件条件下一秒钟只能运行100万次的算法来代替一秒10亿次的算法,那么攻击者可能需要要花1000倍的时间来做暴力破解,60小只将会变成7年!”
你可以自己实现这种方法:

复制代码代码如下:

function myhash($password, $unique_salt) {
$salt = “f#@V)Hu^%Hgfds”;
$hash = sha1($unique_salt . $password);
// make it take 1000 times longer
for ($i = 0; $i < 1000; $i++) {
$hash = sha1($hash);
}
return $hash;
}

你也可以使用一个支持“成本参数”的算法,比如 BLOWFISH。在php中可以用crypt()函数实现:

复制代码代码如下:

function myhash($password, $unique_salt) {
// the salt for blowfish should be 22 characters long
return crypt($password, ‘$2a$10.$unique_salt’);
}

这个函数的第二个参数包含了由”$”符号分隔的几个值。第一个值是“$2a”,指明应该使用BLOWFISH算法。第二个参数“$10”在这里就是成本参数,这是以2为底的对数,指示计算循环迭代的次数(10 => 2^10 = 1024),取值可以从04到31。
举个例子:

复制代码代码如下:

function myhash($password, $unique_salt) {
return crypt($password, ‘$2a$10.$unique_salt’);
}
function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
$password = “verysecret”;
echo myhash($password, unique_salt());
// result: $2a$10$dfda807d832b094184faeu1elwhtR2Xhtuvs3R9J1nfRGBCudCCzC

结果的hash值包含$2a算法,成本参数$10,以及一个我们使用的22位干扰字符串。剩下的就是计算出来的hash值,我们来运行一个测试程序:

复制代码代码如下:

// assume this was pulled from the database
$hash = ‘$2a$10$dfda807d832b094184faeu1elwhtR2Xhtuvs3R9J1nfRGBCudCCzC’;
// assume this is the password the user entered to log back in
$password = “verysecret”;
if (check_password($hash, $password)) {
echo “Access Granted!”;
} else {
echo “Access Denied!”;
}
function check_password($hash, $password) {
// first 29 characters include algorithm, cost and salt
// let’s call it $full_salt
$full_salt = substr($hash, 0, 29);
// run the hash function on $password
$new_hash = crypt($password, $full_salt);
// returns true or false
return ($hash == $new_hash);
}

运行它,我们会看到”Access Granted!”
8.整合起来
根据以上的几点讨论,我们写了一个工具类:

复制代码代码如下:

class PassHash {
// blowfish
private static $algo = ‘$2a’;
// cost parameter
private static $cost = ‘$10’;
// mainly for internal use
public static function unique_salt() {
return substr(sha1(mt_rand()),0,22);
}
// this will be used to generate a hash
public static function hash($password) {
return crypt($password,
self::$algo .
self::$cost .
‘$’. self::unique_salt());
}
// this will be used to compare a password against a hash
public static function check_password($hash, $password) {
$full_salt = substr($hash, 0, 29);
$new_hash = crypt($password, $full_salt);
return ($hash == $new_hash);
}
}

以下是注册时的用法:

复制代码代码如下:

// include the class
require (“PassHash.php”);
// read all form input from $_POST
// …
// do your regular form validation stuff
// …
// hash the password
$pass_hash = PassHash::hash($_POST[‘password’]);
// store all user info in the DB, excluding $_POST[‘password’]
// store $pass_hash instead
// …

以下是登录时的用法:

复制代码代码如下:

// include the class
require (“PassHash.php”);
// read all form input from $_POST
// …
// fetch the user record based on $_POST[‘username’] or similar
// …
// check the password the user tried to login with
if (PassHash::check_password($user[‘pass_hash’], $_POST[‘password’]) {
// grant access
// …
} else {
// deny access
// …
}

9.加密是否可用
并不是所有系统都支持Blowfish加密算法,虽然它现在已经很普遍了,你可以用以下代码来检查你的系统是否支持:

复制代码代码如下:

if (CRYPT_BLOWFISH == 1) {
echo “Yes”;
} else {
echo “No”;
}

不过对于php5.3,你就不必担心这点了,因为它内置了这个算法的实现。
结论
通过这种方法加密的密码对于绝大多数Web应用程序来说已经足够安全了。不过不要忘记你还是可以让用户使用安全强度更高的密码,比如要求最少位数,使用字母,数字和特殊字符混合密码等。