原文:base64编码踩坑之身份证号模糊查询
首先将我在项目中遇到的坑挖出来。在我们的项目中有这样的一个需求就是对于用户的身份证号在数据库中存储的时候不能是明文存储。鉴于这样的一个需求,很自然的我们就想到了用base64编码将身份证号进行编码然后存入数据库中。
经过上面的处理本来以为就没有什么问题了,但是由于我们对base64编码的编码原理并不是很了解,所以就导致我们在接下来的开发过程中掉进了一个我们自己给自己挖的坑中去了。新的需求是这样的:可以通过身份证号的后4位或者后6位模糊查询出用户信息,当然也可以通过完整的身份证号将这个用户的信息查找出来。通过完整的身份证号进行检索当然很容易实现,直接将输入的身份证号也进行base64编码然后去数据库中查找即可。
然而对于身份证号的后4位或者6位编码之后再去数据表中通过like关键字匹配是否可行呢?当然我们开始也是这样去做的。我们想当然的认为,完整的身份证号base64编码之后的字串包含其后4位或者6位base64编码的字串。比如说先编码一个身份证号:
<?php
echo base64_encode('153933199401281934');
// output --- MTUzOTMzMTk5NDAxMjgxOTM0</code>
然后对其后6位编码
<?php
echo base64_encode('281934');
// output --- MjgxOTM0j</code></pre>
看到结果和预想的一样。所以马上就很开心的用like去匹配了。但是当编码后4位的时候却发现结果出现了异常
<?php
echo base64_encode('1934');
// output --- MtkzNA==
在完整的身份证号的base64编码字串中并没有包含后四位的编码字串。那就说明直接使用like去匹配还是行不通的。
这时觉得还是有必要去了解一下base64到底是如何编码的,知己知彼方能百战不殆。
base64编码原理
base64编码之所以叫base64,那是因为它是基于64个字符的编码方式,这64个字符为:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
。
这些字符和数值之间有一个对应表,整个的编码都是按照这个对应表来进行的。
数值 | 字符 | 数值 | 字符 | 数值 | 字符 | 数值 | 字符 |
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 45 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
为什么又要用64个字符呢,因为2的6此方为64,正好可以表示64个字符。举例来说,根据上面的对应表格,000000对应'A',000001对应'B',000002对应'C',111110对应'+'。
然而,我们知道1字节等于8位。当拿到一个字符串的时候,首先找到每个字符对应的ascII码,再将ascII码转换成8位二进制表示('A'=65=01000001)。然后将字符串转换成的二进制串再按照每6位为一组进行划分。这样用每6位二进制数对应的十进制数去上面的对照表中取出相应的字符。举个例子:对'PHP'进行base64编码
原始字符 | P | H | P | |||||||||||||||||||||
ASCII码十进制值 | 80 | 72 | 80 | |||||||||||||||||||||
二进制值 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
每6位一组 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
对应十进制值 | 20 | 4 | 33 | 16 | ||||||||||||||||||||
编码后字符 | U | E | h | Q |
所以字符串'PHP'经过base64编码后为 'UEhQ'。
上面的情况比较特殊,三个字符按照8位为一字节来计算,正好为24位。然后再按照每6位为一组,正好是四组。但是,如果对原始字符串经过二进制转换之后不是24位,而是32位,那再按照每6位为一组进行划分则最后一组只有两位。前面的5组转换成十进制数之后在对照表中找出相应的字符,那最后一组才有两位该如何转换为十进制呢?其实很简单,在其后面用0补充到6位,然后再进行转换。当然,到此还没有结束。虽然每组都补齐了6位,但是经过用0补充以后,总的位数不一定能被8整除。所以说还需要再用'='表示一个6位依次向后补充,直到总的位数能再次被8整除。下面我们在例一的基础上编码'PHPS'。
原始字符 | P | H | P | S | | | ||||||||||||||||||||||||||||||||||||||||||
ASCII码十进制值 | 80 | 72 | 80 | 83 | | | ||||||||||||||||||||||||||||||||||||||||||
二进制值 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | | | ||||||||||||||
每6位一组 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | | | | | | | | | | | | |
对应十进制值 | 20 | 4 | 33 | 16 | 20 | 48 | | | ||||||||||||||||||||||||||||||||||||||||
编码后字符 | U | E | h | Q | U | w | = | = |
我们看到,在上面经过0补充后为36位,不能被8整除。然后再用'='表示一个6位依次向后补充,最后当补充两个'='时为48位,能被8整除。此时编码终止,最后对'PHPS'经过base64编码之后为'UEhQUw=='。
Base64编码主要用在传输、存储、表示二进制等领域,还可以用作简单的加密,但只是一眼看上去不知道什么内容而已,但是可以通过其编码规则对其进行解码。
填坑
通过对base64编码原理的深入了解,发现对于身份证号模糊查询的实现方案根本行不通。既然行不通,那只有反向解码然后在进行模糊查询。
所谓的反向即是在sql中先对要检索的字段进行解码,然后用解码的值去匹配我们要查询的身份证号。这样就需要我们的mysql支持base64编码/解码的聚合函数。经过查找发现在mysqL5.6的版本以及5.6版本之上才会支持。所以我们需要升级我们的mysql至少为5.6版本。
下面是需要的sql
select name,id_card from users where from_base64(id_card) like '%1533';
当然在实际应用中会将1533动态换成我们的输入的身份证号的后4位或者后6位。
这样我们的问题就完满的解决了。我们发现要解决我们的问题其实也很简单,就是一条sql语句就搞定了。但是,找到这个方法的过程还是挺值的。通过这个问题我们深入的了解了base64编码的原理,这对我们以后再在设计程序方面右很大的帮助。