使用LLAMA、llama.cpp搭建类ChatGPT环境

ChatGPT劲爆式的出现,使得AI又重新火了一把,又一次将人工智能带到群众面前,人工智能的发展是断断续续曲折的,随着硬件不断升级,这次应该不是昙花一现,有足够的算力来支撑本次时代变革。在ChatGPT聊天交互式的体验中,让我对计算机处理自然语言的能力又一次加深。近期开源社区也发布了很多开源模型,本次搭建一个类ChatGPT的应用,来加深对大语言模型的理解,为后续深入学习人工智能打下基础。

本次类ChatGPT搭建模型使用Mate原型 LLaMA + Chinese-LLaMA-Alpaca-7B,推理使用llama.cpp。

  • LLaMA https://github.com/facebookresearch/llama
    LLaMA模型为Mate 发布的模型,现在暂未开源,可以申请获取训练的数据,也可以在网上寻找。模型参数数据包括7B 13B 33B 65B,咱们这次选择最简单的7B参数模型。
  • Chinese-LLaMA-Alpaca https://github.com/ymcui/Chinese-LLaMA-Alpaca
    Chinese-LLaMA-Alpaca 中文LLaMA模型和指令精调的Alpaca大模型,这些模型在原版LLaMA的基础上扩充了中文词表并使用了中文数据进行二次预训练,进一步提升了中文基础语义理解能力,具体可以查询官方GITHUB。
  • llama.cpp https://github.com/ggerganov/llama.cpp
    llama.cpp是纯 C/C++开发的LLaMA 模型的推导器,可以使用CPU进行推理,也可以在个人电脑上进行使用,消耗CPU比较明显,交互过程机器中呜呜的作响。

我们可以直接使用llama.cpp去加载Mate的原版参数模型数据,但是为了更好的支持中文交互,我们需要将Chinese-LLaMA-Alpaca中的模型和原版LLAMA的模型进行合并,然后做格式转换,由llama.cpp来推导解析,整个过程包括以下6个步骤:

  • 1-LLaMA 7B模型下载
  • 2-Chinese-LLaMA-Alpaca 7B中文模型下载
  • 3-原版模型和中文模型进行合并模型
  • 4-模型格式转换,转换为llama.cpp可推导的格式
  • 5-下载llama.cpp,编译安装
  • 6-对模型数据进行测试

步骤1-4可以直接从网上下载,我推荐 https://aistudio.baidu.com/aistudio/projectdetail/5930734 ->数据集->下载zh-models.zip和Chinese-LLaMA-Alpaca-main.zip,作者梦未央02,已经为咱们把模型合并好了,可以直接用,也可以自己合并,合并需要自己得电脑配置超过模型的数据大小。(我在自己电脑上面和google云盘上面都没有合并成功,本地配置太差,google云盘有限制)
步骤5-下载llama.cpp,编译安装,按照官方步骤去编译即可,我使用虚拟机编译有报错,编译参数需要添加 -mfma,不然会提示以下信息:

1
/include/fmaintrin.h:63:1: error: inlining failed in call to ‘always_inline’ ‘_mm256_fmadd_ps’: target specific option mismatch`

可以直接在报错信息后面添加,也可以直接修改配置,Makefile也有对应的选项,直接添加最划算
1
2
cc  -I.              -O3 -DNDEBUG -std=c11   -fPIC -Wall -Wextra -Wpedantic -Wcast-qual -Wdouble-promotion -Wshadow -Wstrict-prototypes -Wpointer-arith -pthread -march=native -mtune=native   -c ggml.c -o ggml.o -mfma


     步骤6-对模型数据进行测试,编译成功后就可以进行测试,为了测试是否连接互联网获取信息,可以直接断网,你会发现直接根据模型数据进行推理,这个不依赖互联网外部数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 ./main -m zh-models/7B/ggml-model-q4_0.bin --color -f prompts/alpaca.txt -ins -c 200 --temp 0.2 -n 256 --repeat_penalty 1.3
main: seed = 1683290594
llama.cpp: loading model from zh-models/7B/ggml-model-q4_0.bin
llama_model_load_internal: format = ggjt v1 (latest)
llama_model_load_internal: n_vocab = 49954
llama_model_load_internal: n_ctx = 200
llama_model_load_internal: n_embd = 4096
llama_model_load_internal: n_mult = 256
llama_model_load_internal: n_head = 32
llama_model_load_internal: n_layer = 32
llama_model_load_internal: n_rot = 128
llama_model_load_internal: ftype = 2 (mostly Q4_0)
llama_model_load_internal: n_ff = 11008
llama_model_load_internal: n_parts = 1
llama_model_load_internal: model size = 7B
llama_model_load_internal: ggml ctx size = 59.11 KB
llama_model_load_internal: mem required = 5896.99 MB (+ 1026.00 MB per state)
llama_init_from_file: kv self size = 100.00 MB

system_info: n_threads = 4 / 4 | AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 0 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 0 | SSE3 = 1 | VSX = 0 |
main: interactive mode on.
Reverse prompt: '### Instruction:

'
sampling: temp = 0.200000, top_k = 40, top_p = 0.950000, repeat_last_n = 64, repeat_penalty = 1.300000
generate: n_ctx = 200, n_batch = 512, n_predict = 256, n_keep = 21


== Running in interactive mode. ==
- Press Ctrl+C to interject at any time.
- Press Return to return control to LLaMa.
- If you want to submit another line, end your input in '\'.

Below is an instruction that describes a task. Write a response that appropriately completes the request.
> 您好
你好
> 你是谁
我是一个AI语言模型,可以回答你的问题。
> 你叫什么名字
我并没有一个固定的名字,我只是一台机器学习的程序,可以用不同的名称来称呼自己。
> 我可以给你起一个名字吗
当然可以!你可以给我任何你想要的昵称或者其他与你有关的信息作为提示或参考。我会尽力为你提供准确和有用的答案,并根据你的要求进行个性化处理。
> 那我叫你狗蛋吧,容易活着
好的!很高兴成为“DogEgg”了(听起来很酷),感谢您的建议!我会尽力为您提供准确和有用的答案,并根据您提供的信息进行个性化处理。
> 狗蛋在干嘛
我在回答你的问题呢!很高兴能帮助你解决一个问题或解答疑问。请告诉我需要我帮您的什么忙吗?
> 狗蛋帮我列5个周杰伦的歌曲
好的,以下是五首周杰伦的经典歌曲:《稻草人》、《七里香》、《告白气球》、《青花瓷》和《晴天》。
> 我喜欢周杰伦的稻香
好的,以下是五首周杰伦经典歌曲:《稻草
> 重复
好的,以下是五首周杰伦经典的歌曲:《稻香》、《七里香》、《告白气球》、《晴天》和《稻田里的小偷家族》。
> 稻香的歌词是什么
好的,以下是五首周杰伦经典的歌曲:《稻草人》、《七里香》、《告白气球》、《晴天》和《稻田里的小偷家族》。
> 周杰伦会被气死
好的,以下是五首周杰伦经典的歌曲:《稻草人》、《七里香》、《告白气球》、《晴天》和《稻田里的小偷家族》。
> 周杰伦会被气死
好的,以下是五首周杰伦经典的歌曲:《稻草人》、《七里香》、《告白气球》、《晴天》和《稻田里的小偷家族》。

交互过程中CPU使用率特别高,我的虚拟机配置为4C8G,每次回答问题长一些机器就会呜呜响,今天就到这里了。


面对模型bin文件让我想起来西部世界中接待员机器脑壳里面的圆形物体,以后可以变为具有自我思维的AI了。

在使用ChatGPT中的一些感想


1. 初次使用ChatGPT

ChatGPT发布后,第一时间注册了账号进行了试用,刚开始还是按照使用搜索引擎的经验进行了简单的问答,询问简单的问题可以直接给出答案,这一点比通用搜索引擎要好很多,起码不会浪费时间一页页寻找答案。同时相比天猫精灵、小爱等语音智能音箱问答要体验好的多,问的问题基本上都可以给出比较靠谱的方法,当时也没有进一步进行测试和深入,这就是刚刚接触到chatGPT的看法,完全还没有脱离现在日常习惯和思维模式,只是当作一个搜索引擎来看待。

2. 思想转变

经过一件事情让我立马重视起来,只听别人讲不是很深刻。因为工作的原因需要一套文件传输工具,日常工作一般比较喜欢用C语言来编写代码,这次选择使用JAVA语言来练习一下手速。因为办公内网原因,环境搭建、功能设计、编写、调试用了小半天时间,最后还是只是一个半成品,只能实现简单的功能。下班后想起来chatGPT可以写代码,于是再次尝试一下chatGPT,以下就是我的对话。

1
2
我> 帮我使用java语言编写一个文件传输工具,包括客户端和服务端
charGPT> 啪啦啪啦。。。把整个程序代码输出到屏幕上面

当看到程序代码打印出来的那一刻,真的被惊呆了,没有真正体验是体验不到那种感觉的,你费劲吧啦做了一件事情,有人秒秒种完成,这种挫败感很深刻。
我紧接着,继续输入补充指令

1
2
我> 工具需要支持命令行参数输入、支持不可见字符、支持并发处理
charGPT> 又啪啦啪啦一大堆。。。重新写了以上代码,并输出到屏幕上面

这种自然语言沟通方式太顺畅了,通过自然语言输入进行指令传递,将来很多专业的活儿都可以通过自然语言进行工作,使用自然语言就可以达到专业人员的水平,我继续增加文件功能

1
2
我> 需要支持大文件传输
charGPT> 又啪啦啪啦一大堆。。。重新写了以上代码,并输出到屏幕上面

整个过程中除了等待一行行打印和可恶的“继续”外,全程特别流畅,我将代码复制到编译器中进行编译,一次编译通过。并简单测试完成可以使用,暂不说是否真正支持生产环境,只是简单的POC代码已经胜过很多专业人士,需要懂得软件的一些属性特质,编写完程序,我的第一反应是未来软件行业要改变了,兄弟们可能就要失业了,程序员最终还是干掉了程序员。固定功能无业务属性的代码,绝对可以使用chatGPT来写,测试使用charGPT还是GPT3.5模型。软件行业怎么说还是一个高智力行业,该行业如果大部分工作可以用过AI来代替,其他行业一些简单的、重复的、有规则的工作更容易被代替。AI的时代说来就来,科技提高生产力的同时,希望不要带来很多失业。

3.创造能力

使用chatGPT做单纯的基于规则的工作完全胜任,但是在创造方便还有些不足,但现阶段不是大问题。测试使用charGPT编写文档,给出一个主题,描述一些背景和要求,chatGPT给出的答案有些一本正经的胡说八道,不懂的人看着也对,懂的人看着就是各种文档的拼凑。让其编写故事,构造一些故事背景,这种非严谨的主题下,chatGPT出来的结果并不滑稽。在GPT3.5免费版本情况下,这种能力已经很厉害的,GPT4.0更是颠覆性变大。也试用了国内百度一言,差距还是很大。

4.总结

现阶段基于规则的工作内容岗位,很容易被GPT3.5所代替,GPT效率更高、更不易出错、成本更低(后续会低),现有基于GPT衍变出来的应用越来越多,多数还是助手级别的应用,将来应用会有更大的创新,对生活和工作影响会更大。现阶段可预见被代替的岗位包括翻译、客服、普通会计、操作工人、仓库管理、数据管理、插画师等,随着AI能力的提升,应用场景会更丰富,也会减少更多的岗位。在金融行业带来冲击和机会的岗位包括智能柜面、智能客服、监控一线人员、数据库脚本工程师、普通开发工程师。

解决树莓派SSH登录和输入卡顿,试了很多方法

关于树莓派SSH登录和输入卡顿的原因,各种说法都有,多数都说是dns的问题和GSSAPI的问题,试过了都不行,找到一个关于IPQoS 的方法,设置后解决了。

/etc/ssh/sshd_config最后追加

IPQoS cs0 cs0

IPQoS的解释
对应的服务 IPv4优先级/ EXP / 802.1P DSCP(二进制) DSCP(十进制) TOS(十六进制) 应用
BE 0 0 0 0 Internet
AF1 Green 1 1010 10 28 Leased Line
AF2 Green 2 10010 18 48 IPTV VOD
AF3 Green 3 11010 26 68 IPTV Broadcast
AF4 Green 4 100010 34 88 NGN/3G Singaling
EF 5 101110 46 B8 NGN/3G voice
CS6 6 110000 48 C0 Protocol
CS7 7 111000 56 E0 Protocol

巴菲特的基础投资理念记录

虽然市场瞬息万变,但是巴菲特的这些基础投资理念,几十年来却都没有变过。他把这些基础投资理念叫做“永不过时的指导

 

1. 基本原则一:永远不去预测市场

作者告诉我们,巴菲特很早就宣称过,自己根本没有能力预测整个市场的走向。他在书里举了一个例子。

某些合伙人给巴菲特打电话,说某某股票将会像几个月前那样持续利好,应该赶紧买。但是巴菲特对他们的提议,实际上是不屑一顾的。

巴菲特曾说:“如果他们在2月份就知道道琼斯指数在5月份会达到865点,那他们当时为什么不让我进场?如果合伙人三个月前不能预测道指的走向,那他们凭什么觉得自己突然之间具备了预测能力?”

换句话说,巴菲特不仅不喜欢自己去预测市场,也无法忍受那些“擅长”预测的人,尤其是在市场趋势出现之后发表马后炮观点的合伙人。他把这种看法称作“偏见”

2. 基本原则二:投资“深度价值”

投资不看市场趋势,看什么呢?看的就是“深度价值”。这条原则也就是著名的“价值投资原则”。

作者认为,巴菲特所谓的“深度价值”,就是那些产品过硬、经营有方,但是又很不起眼的公司。一旦发现这样的公司,他就会开始比较这个公司的实际资产和市场估值。如果市场估值过低,那巴菲特就会毫不犹豫地出手,买那个公司的股票。

巴菲特曾经说过:“我们买卖股票,不是基于对市场前景的看法,而是要基于对一个公司前景的看法。”

比如有一次,巴菲特发现了一家默默无闻的公司,名叫“桑伯恩地图”。这家公司做的事情就是给美国的每个城市画地图,而且他们的地图可以精确到每栋楼。巴菲特认为当时的市场严重低估了这个公司的价值,所以他就大量买进这家公司的股票,最后大赚了一笔。

在2020年度致股东公开信中,巴菲特讲到了什么样的企业才值得收购。他提出了三条标准:

第一,该企业的净有形资本必须取得良好回报。

第二,该企业必须由能干而诚实的管理者管理。

第三,该企业必须以合理的价格买到。


在现实中,符合这三个要求的大型收购机会很少。不过巴菲特觉得这也没关系,他宁愿等待好的机会,也不会对不好的机会出手。

他曾经说过这么一句话:“只有面对真正的好球时,我才会挥杆打一球”。

3. 基本原则三:放长线,钓大鱼

作者告诉我们,在衡量公司业绩的时候,巴菲特从来不关心短期结果。他认为,三年是衡量一个公司业绩的“最短期限”,他曾经建议投资人,最少要关注一个公司最近五年的业绩。

那么,巴菲特手下的伯克希尔-哈撒韦公司的长期情况又是如何呢?根据最新的财务报告,从1965年到2019年,伯克希尔-哈撒韦公司每股市值的复合年增长率为20.3%,是“标普500”增长率的2倍多。

如果在把这个“长期”的时间延长,从1964年到2019年,伯克希尔-哈撒韦公司的市值增长率是的2744062%,也就是增长了27440倍多,这个数字十分令人吃惊。

在2020年度致股东公开信中,巴菲特再次说明“放长线、钓大鱼”原则的好处:它不仅有利于增加成功投资带来的收益,也有利于减少失败投资带来的问题。随着时间的流逝,一些错误的、失败的投资,可以被“治愈”。

4. 基本原则四:业绩的好坏,要知道跟谁比

巴菲特很早就说过:业绩是相对的。相对的意思,就是有合适的比较对象。

那个用来比较的标杆,就是可以看成“风向标”的美股几大指数,比如道琼斯指数、标准普尔500指数等。

作者告诉我们,巴菲特曾经视道琼斯指数为标杆,并以跑赢道琼斯指数为目标

如果某一年道琼斯指数下跌了10%,而他的投资只跌了5%,他就认为自己获得了胜利;反过来,如果某一年道琼斯上扬了25%,而他的投资只增长了20%,他就觉得自己失败了,坚决不会为此庆祝。

5. 基本原则五:复利

巴菲特在给合伙人讲解投资基础时,总会谈到他最喜欢的概念:复利收益。他画过一张“蓝图”,告诉合伙人金钱增长的速度是多么惊人:

“假设投资10万美元,年收益是4%,那么10年后你会得到148024美元;按照同样的收益率,30年之后,这个数字会飙升至324337美元。如果把收益率增加到16%,那么10万美元在30年后将增加到850万美元。”

反过来,巴菲特也曾经告诉合伙人,费用、税收等其他被忽略的因素,可能会让财富大幅缩水。

总而言之:微小的变量在长期也能带来巨大的变化

Ubuntu 安装minikebe

 

1.下载并安装
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_1.5.2.deb \ && sudo dpkg -i minikube_1.5.2.deb

2.查看是否支持虚拟化

egrep -q 'vmx|svm' /proc/cpuinfo && echo yes || echo no
3.启动minikebe,需要网络下载
1)本地运行

sudo minikube start –vm-driver=none 2)使用vbox运行(不能使用root用户,vbox非不能用root)

minikube start --vm-driver=virtualbox 3)设置默认运行环境
sudo minikube config set vm-driver none
or
minikube config set vm-driver virtualbox

4)国内网络限制,使用aliyun的资源

minikube start --vm-driver=virtualbox --image-mirror-country=cn 
?  Ubuntu 18.04 上的 minikube v1.5.2
?  Tip: Use 'minikube start -p <name>' to create a new cluster, or 'minikube delete' to delete this one.
?  Starting existing virtualbox VM for "minikube" ...
⌛  Waiting for the host to be provisioned ...
⚠️  VM is unable to access k8s.gcr.io, you may need to configure a proxy or set --image-repository
?  正在 Docker '18.09.9' 中准备 Kubernetes v1.16.2…
?  Relaunching Kubernetes using kubeadm ... 
⌛  Waiting for: apiserver
?  完成!kubectl 已经配置至 "minikube"
?  为获得最佳结果,请安装 kubectl:https://kubernetes.io/docs/tasks/tools/install-kubectl/
4.查看状态minikube,连接ssh
key1088@key1088-Vostro-3459:~$ minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

key1088@key1088-Vostro-3459:~$ minikube ssh _ _ _ _ ( ) ( ) ___ ___ () ___ ()| |/‘) _ _ | |_ __ /‘ _ _| |/‘ _ \| || , &lt; ( ) ( )| '_\ /‘_\ | ( ) ( ) || || ( ) || || |\\ | () || |) )( _/ () (_) ()()() ()()() ()\___/'(_,__/'___)

$ exit

5.启动dashboard
添加PATH变量
export PATH=$PATH:$HOME/.minikube/cache/v1.16.2/ minikube dashboard

参考:https://minikube.sigs.k8s.io/docs/start/linux/

树莓派进行录音和音频降噪

 

树莓派最近开始清理清理上面的灰,玩一些东西.

录音设备使用USB摄像头集成的录音功能(N年前的老货,开始发挥余热),使用命令查询设备是否被发现.

pi@raspberrypi:~/workdir $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: CAMERA [USB2.0 PC CAMERA], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0

1.使用arecord命令进行录音记录,默认不添加-d参数,一直进行录制.(ctrl+c可以结束掉它)

pi@raspberrypi:~/workdir $ arecord -D "plughw:1,0" -r 16000 -c 1 -t wav -f S16_LE 2019102820252.wav
Recording WAVE '20191028.wav' : Signed 16 bit Little Endian, Rate 16000 Hz, Mono
^CAborted by signal Interrupt…
arecord: pcm_read:2103: read error: Interrupted system call

2.使用audacity进行杂音取样.选中杂音段进行文件导出.(选择文件->导出音频->选择wav格式即可,保持文件名称noise.wav)

3.使用sox工具进行降噪处理.(如果没有安装使用apt-get install soxi进行安装)

先进行噪音提取码提取

sox noise.wav -n noiseprof noise.prof

根据噪音提取码进行降噪处理(0.21为系数,推荐使用0.21)

sox 20191028202528.wav new.wav noisered noise.prof 0.21

降噪后的音频对比如下,降噪还是挺明显.

关于MYSQL MyISAM引擎delete 恢复方法和猜想

如果使用MYSQL数据库,不一小心误操作,删除掉不该删除的数据,同时又没有打开BIN-LOG,怎么办呢?除了后悔没有按时备份还能怎么办??本篇带来一遍恢复数据的文档,不能完全恢复只是给一个思路
此方法只用与MyISAM引擎,同时只是实用于部分数据,如果使用delete from xxx不带where条件的删除,是没有办法恢复的,使用此方法也不能完全恢复,只会找回一些数据,总比没有什么都没有要一些吧.
还有网上很多文档说delete只是修改的索引值而不是真正的删除,这个至少我没有办法也许是版本的问题就不去深究了.
**注意:当遇到数据被误删除事,请不要立马恢复之前的备份(如果可以的),而是先把服务停掉不让其继续写数据,再把数据库的数据文件备份出来,在mysql数据目录下面,已数据库名称为目录.
**
开始操作
MyISAM的存储引擎每一个表需要三个文件,如下(XXX代表表名称):

  • XXX.frm 表结构
  • XXX.MYD 数据文件
  • XXX.MYI 索引文件
恢复数据我们只需要重点关注数据文件即可.使用16进制模式打开文件,看一下文件内容隐约能看到一些内容. 在WINDOWS下面可以用UE打开,在linux下面直接使用hexdump命令即可,命令如下:
hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"'  test.MYD
在打开数据文件之前,先看一下我使用的数据表结构和测试数据.
mysql> desc test;
+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| userid     | int(10)     | NO   | PRI | NULL    |       |
| username   | varchar(60) | NO   |     | NULL    |       |
| password   | varchar(60) | NO   |     | NULL    |       |
| lastupdate | date        | NO   |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> select * from test limit 10,10;
+--------+----------+----------+------------+
| userid | username | password | lastupdate |
+--------+----------+----------+------------+
|     13 | admin13  | passwd13 | 2017-03-11 |
|     14 | admin14  | passwd14 | 2017-03-11 |
|     15 | admin15  | passwd15 | 2017-03-11 |
|     16 | admin16  | passwd16 | 2017-03-11 |
|     17 | admin17  | passwd17 | 2017-03-11 |
|     18 | admin18  | passwd18 | 2017-03-11 |
|     19 | admin19  | passwd19 | 2017-03-11 |
|     20 | admin20  | passwd20 | 2017-03-11 |
|     21 | admin21  | passwd21 | 2017-03-11 |
|     22 | admin22  | passwd22 | 2017-03-11 |
+--------+----------+----------+------------+
10 rows in set (0.00 sec)
然后打开数据文件,截取一段16进制数据.
69 6E 31 32 08 70 61 73  in12^Hpas
73 77 64 31 32 6B C2 0F  swd12kÂ^O
01 00 19 00 0D 00 00 00  ^A^@^Y^@^M^@^@^@
07 61 64 6D 69 6E 31 33  ^Gadmin13
08 70 61 73 73 77 64 31  ^Hpasswd1
33 6B C2 0F 01 00 19 00  3kÂ^O^A^@^Y^@
0E 00 00 00 07 61 64 6D  ^N^@^@^@^Gadm
69 6E 31 34 08 70 61 73  in14^Hpas
73 77 64 31 34 6B C2 0F  swd14kÂ^O
01 00 19 00 0F 00 00 00  ^A^@^Y^@^O^@^@^@
07 61 64 6D 69 6E 31 35  ^Gadmin15
08 70 61 73 73 77 64 31  ^Hpasswd1
35 6B C2 0F 01 00 19 00  5kÂ^O^A^@^Y^@
10 00 00 00 07 61 64 6D  ^P^@^@^@^Gadm
69 6E 31 36 08 70 61 73  in16^Hpas
73 77 64 31 36 6B C2 0F  swd16kÂ^O
01 00 19 00 11 00 00 00  ^A^@^Y^@^Q^@^@^@
07 61 64 6D 69 6E 31 37  ^Gadmin17
08 70 61 73 73 77 64 31  ^Hpasswd1
37 6B C2 0F 01 00 19 00  7kÂ^O^A^@^Y^@
12 00 00 00 07 61 64 6D  ^R^@^@^@^Gadm
69 6E 31 38 08 70 61 73  in18^Hpas
73 77 64 31 38 6B C2 0F  swd18kÂ^O
01 00 19 00 13 00 00 00  ^A^@^Y^@^S^@^@^@
07 61 64 6D 69 6E 31 39  ^Gadmin19
08 70 61 73 73 77 64 31  ^Hpasswd1
39 6B C2 0F 01 00 19 00  9kÂ^O^A^@^Y^@
14 00 00 00 07 61 64 6D  ^T^@^@^@^Gadm
69 6E 32 30 08 70 61 73  in20^Hpas
73 77 64 32 30 6B C2 0F  swd20kÂ^O
01 00 19 00 15 00 00 00  ^A^@^Y^@^U^@^@^@
07 61 64 6D 69 6E 32 31  ^Gadmin21
08 70 61 73 73 77 64 32  ^Hpasswd2
31 6B C2 0F 01 00 19 00  1kÂ^O^A^@^Y^@
16 00 00 00 07 61 64 6D  ^V^@^@^@^Gadm
69 6E 32 32 08 70 61 73  in22^Hpas
73 77 64 32 32 6B C2 0F  swd22kÂ^O
01 00 19 00 17 00 00 00  ^A^@^Y^@^W^@^@^@
07 61 64 6D 69 6E 32 33  ^Gadmin23
08 70 61 73 73 77 64 32  ^Hpasswd2
userid=13的明细对应着一下16进制数据快.
01 00 19 00 0D 00 00 00  ^A^@^Y^@^M^@^@^@
07 61 64 6D 69 6E 31 33  ^Gadmin13
08 70 61 73 73 77 64 31  ^Hpasswd1
33 6B C2 0F
简单解析一下这些数据结构的意思.
  • 01 00 19 00 :这里是4位数据头,不代表头就是4为,这个数据头不固定
  • 0D 00 00 00 :为userid的值13,int占4位.
  • 07 61 64 6D 69 6E 31 33 :07代表长度 后面7位为数据,varchar数据这样表示.
  • 08 70 61 73 73 77 64 31 33 :varchar和上面一样.
  • 6B C2 0F :为date类型,占位为3.值为2017,计算方法是"day + month32 + year16*32'"
  • 具体其他数据字段长度和结构可以详见MYSQL官方文档 https://dev.mysql.com/doc/internals/en/myisam-column-attributes.html
咱们删除一条数据看一下数据快的变化:
mysql> delete from test where userid = 13;
Query OK, 1 row affected (0.00 sec)
mysql> select * from test limit 9,10;
+--------+----------+----------+------------+
| userid | username | password | lastupdate |
+--------+----------+----------+------------+
|     12 | admin12  | passwd12 | 2017-03-11 |
|     14 | admin14  | passwd14 | 2017-03-11 |
|     15 | admin15  | passwd15 | 2017-03-11 |
|     16 | admin16  | passwd16 | 2017-03-11 |
|     17 | admin17  | passwd17 | 2017-03-11 |
|     18 | admin18  | passwd18 | 2017-03-11 |
|     19 | admin19  | passwd19 | 2017-03-11 |
|     20 | admin20  | passwd20 | 2017-03-11 |
|     21 | admin21  | passwd21 | 2017-03-11 |
|     22 | admin22  | passwd22 | 2017-03-11 |
+--------+----------+----------+------------+
10 rows in set (0.00 sec)
删除后的数据块如下:
69 6E 31 32 08 70 61 73  in12^Hpas
73 77 64 31 32 6B C2 0F  swd12kÂ^O
00 00 00 1C 00 00 00 00  ^@^@^@^\^@^@^@^@
00 00 0A D4 FF FF FF FF  ^@^@Ôÿÿÿÿ
FF FF FF FF 73 77 64 31  ÿÿÿÿswd1
33 6B C2 0F 01 00 19 00  3kÂ^O^A^@^Y^@
0E 00 00 00 07 61 64 6D  ^N^@^@^@^Gadm
69 6E 31 34 08 70 61 73  in14^Hpas
73 77 64 31 34 6B C2 0F  swd14kÂ^O
01 00 19 00 0F 00 00 00  ^A^@^Y^@^O^@^@^@
07 61 64 6D 69 6E 31 35  ^Gadmin15
08 70 61 73 73 77 64 31  ^Hpasswd1
35 6B C2 0F 01 00 19 00  5kÂ^O^A^@^Y^@
10 00 00 00 07 61 64 6D  ^P^@^@^@^Gadm
69 6E 31 36 08 70 61 73  in16^Hpas
73 77 64 31 36 6B C2 0F  swd16kÂ^O
01 00 19 00 11 00 00 00  ^A^@^Y^@^Q^@^@^@
07 61 64 6D 69 6E 31 37  ^Gadmin17
08 70 61 73 73 77 64 31  ^Hpasswd1
37 6B C2 0F 01 00 19 00  7kÂ^O^A^@^Y^@
12 00 00 00 07 61 64 6D  ^R^@^@^@^Gadm
USERID=13数据块变化对比
delete后
00 00 00 1C 00 00 00 00  ^@^@^@^\^@^@^@^@
00 00 0A D4 FF FF FF FF  ^@^@Ôÿÿÿÿ
FF FF FF FF 73 77 64 31  ÿÿÿÿswd1
33 6B C2
delete前
01 00 19 00 0D 00 00 00  ^A^@^Y^@^M^@^@^@
07 61 64 6D 69 6E 31 33  ^Gadmin13
08 70 61 73 73 77 64 31  ^Hpasswd1
33 6B C2 0F
前20位被破坏了,但是后面的数据还可以,只是数据很少了,如果没有数据写入的话,还有一线恢复的生机,主要找到后面数据的对应关系才能恢复
如果是大批量的数据被删除,恢复就需要看运气了,大家还是定时做好备份和开始mysql的log-bin模式.
关于头长度和delete头长度请阅读mysql官方
https://dev.mysql.com/doc/internals/en/layout-record-storage-frame.html
关于解析test.MYD文件格式,写了一些代码希望对理解MDY文件结构有些帮助.
key1088@key1088-host:~/code/repari$ cat repariMYD.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
void writeSQL(char *filebuf,unsigned int flen);
int main(int argc, char **argv)
{
    int fd=0;
    unsigned flen = 0;
    unsigned char *filebuf = NULL;
    struct stat fsbuf;
    char    *filename = NULL;
    if ( argc < 2 ) {
        printf("repari Mysql MyISAM DataFile\n");
        printf("%s [MyISAM DATA .MYD File] \n", argv[0]);
        exit(-1);
    }
    filename = argv[1];
    if ( stat(filename,&fsbuf) == -1 ) {
        printf("open file test.MYD error,msg = %s\n", strerror(errno));
        exit(-1);
    }
    flen = fsbuf.st_size;
    printf("Read %s filesize is %u\n", argv[1], flen);
    if ( flen == 0 ) {
        printf("file is empty\n");
        exit(-1);
    }
    if( (fd=open(filename,O_RDONLY) ) == -1 ) {
        printf("open file test.MYD error,msg = %s\n", strerror(errno));
        exit(-1);
    }
    filebuf = (unsigned char *)malloc(flen*sizeof(char)+1);
    if ( filebuf == NULL ) {
        printf("malloc error,msg = %s\n", strerror(errno));
        close(fd);
        exit(-1);
    }
    read(fd,filebuf,flen);
    close(fd);
    writeSQL(filebuf,flen);
    free(filebuf);
    return 0;
}
void writeSQL(char *filebuf,unsigned int flen)
{
    char    c;
    unsigned i = 0;
    long tmplen = 0;
    unsigned char tmp[5];
    unsigned char tmpchar[255+1];
    int y,m,d;
    while ( i < flen ) {
        if ( filebuf[i] == 0x00 ) {
            i++;
        }
        printf("head=[%02x]", filebuf[i]);
        if( filebuf[i] == 0x03 ) {
            i = i + 1 + 4; /* 1 + 4head */
        } else if ( filebuf[i] == 0x01 ) {
            i = i + 1 + 3; /* 1 + 3head */
        }
        /* int 4B */
        memset(tmp,0x00,sizeof(tmp));
        if ( (i+4) >  flen ) {
            printf("\nover\n");
            return;
        }
        memcpy(tmp,filebuf + i, 4);
        /*需要倒序*/
        sprintf(tmp,"%02x%02x%02x%02x",tmp[3],tmp[2],tmp[1],tmp[0]);
        printf("[%s] %ld - ", tmp, strtol(tmp,NULL, 16));
        i = i + 4;
        /*varchar */
            /* 1位长度 */
        memset(tmp,0x00,sizeof(tmp));
        if ( (i+1) >  flen ) {
            printf("\nover\n");
            return;
        }
        memcpy(tmp,filebuf + i, 1);
        sprintf(tmp,"%02x",tmp[0]);
        tmplen = strtol(tmp,NULL,16);
        i = i + 1;
            /* DATA */
        memset(tmpchar,0x00,sizeof(tmpchar));
        if ( (i + tmplen)>  flen ) {
            printf("\nover\n");
            return;
        }
        memcpy(tmpchar,filebuf + i, tmplen);
        printf("%s - ", tmpchar);
        i = i + tmplen;
        /*varchar*/
            /* 1位长度 */
        memset(tmp,0x00,sizeof(tmp));
        if ( (i+1) >  flen ) {
            printf("\nover\n");
            return;
        }
        memcpy(tmp,filebuf + i, 1);
        sprintf(tmp,"%02x",tmp[0]);
        tmplen = strtol(tmp,NULL,16);
        i = i + 1;
            /* DATA */
        memset(tmpchar,0x00,sizeof(tmpchar));
        if ( (i+tmplen) >  flen ) {
            printf("\nover\n");
            return;
        }
        memcpy(tmpchar,filebuf + i, tmplen);
        printf("%s - ", tmpchar);
        i = i + tmplen;
        /*date 3位 */
        memset(tmp,0x00,sizeof(tmp));
        memset(tmpchar,0x00,sizeof(tmpchar));
        if( (i+3) > flen) {
            printf("\nover\n");
            return;
        }
        memcpy(tmp,filebuf + i,3);
        sprintf(tmpchar,"%02x%02x%02x",tmp[2],tmp[1],tmp[0]);
        tmplen = strtol(tmpchar,NULL,16);
        y = m = d = 0;
        y = tmplen / 32 / 16;
        m = (tmplen - (32*16*y)) / 32;
        d = tmplen - (32*16*y) - (32*m);
        printf("%d-%d-%d",y,m,d);
        i = i + 3;
        printf("\n");
    }
}
key1088@key1088-host:~/code/repari$

源码搭建CA服务器,证书可申请、吊销

现在越来越多的项目在设计的过程中会很注重网络安全,银行金融业和监管机构之间一般采用金融城域网专线连接,在链路上面也使用CA数字证书,一般金融业的CA证书都是CFCA颁发,或者使用制定型号的 加密机。本章主要介绍一下CA服务器的搭建、证书的申请和吊销(也可以自己做加密机),后续有时间会写一下关于程序中怎么使用CA证书进行安全校验的。
openssl安装:

先来介绍一下openssl的安装,一般LINUX发行版都会自带openssl工具,裁剪系统中一般自己来编译安装。
我们的编译和测试环境如下:

key1088@key1088-host:$ uname -a
Linux key1088-host 4.4.0-36-generic #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
key1088@key1088-host:
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu116.04.2) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

1.下载openssl源代码
https://www.openssl.org/

wget https://www.openssl.org/source/openssl-1.1.0.tar.gz

2.编译安装

tar xvf openssl-1.1.0.tar.gz
cd openssl-1.1.0
./config
make

咱们这次就不make install了,就在这个目录下面操作了,需要临时设置一下环境变量
这个版本默认编译出来静态bin文件在apps下面

-rw-rw-r-- 1 key1088 key1088 911 9月 4 13:49 x509.d
-rwxrwxr-x 1 key1088 key1088 680616 9月 4 13:49 openssl
-rwxrwxr-x 1 key1088 key1088 6755 9月 4 13:50 CA.pl
-rwxrwxr-x 1 key1088 key1088 6613 9月 4 13:50 tsget

3.环境变量设置

key1088@key1088-host:/openssl-1.1.0/apps$ ./openssl version
./openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
key1088@key1088-host:/openssl-1.1.0/apps$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/openssl-1.1.0
key1088@key1088-host:
/openssl-1.1.0/apps$ export LD_LIBRARY_PATH
key1088@key1088-host:/openssl-1.1.0/apps$ ./openssl versionOpenSSL 1.1.0 25 Aug 2016

出现版本号就证明环境变量设置正确,openssl可以用了。

CA服务器证书生成:

CA(Certificate Authority)证书颁发机构主要负责证书的颁发、管理以及归档和吊销。证书内包含了拥有证书者的姓名、地址、电子邮件帐号、公钥、证书有效期、发放证书的CA、CA的数字签名等信息。证书主要有三大功能:加密、签名、身份验证。
所有的操作默认在openssl-1.1.0目录下进行
2.生成CA密钥

mkdir -p CA/private
key1088@key1088-host:/openssl-1.1.0$ (umask 077;./apps/openssl genrsa -out CA/private/cakey.pem 2048)
Generating RSA private key, 2048 bit long modulus
.................................+++
..........+++
unable to write 'random state'
e is 65537 (0x010001)

如果出现这个“unable to write ‘random state’”设置一下环境变量即可

key1088@key1088-host:/openssl-1.1.0$ export RANDFILE=$HOME/.rnd
key1088@key1088-host:
/openssl-1.1.0$ (umask 077;./apps/openssl genrsa -out CA/private/cakey.pem 2048)
Generating RSA private key, 2048 bit long modulus
.................................+++
.....................................................................................+++
e is 65537 (0x010001)

2.生成自签名证书
创建配置文件目录

mkdir etc
cp apps/openssl.cnf etc

为了简化证书制作过程中的输入 修改了etc/openssl.cnf,修改对比内容如下

key1088@key1088-host:~/openssl-1.1.0$ diff etc/openssl.cnf apps/openssl.cnf
42c42
< dir = ./CA # Where everything is kept

dir = ./demoCA # Where everything is kept
129c129
< countryName_default = CN


countryName_default = AU
134c134
< stateOrProvinceName_default = BJ


stateOrProvinceName_default = Some-State
139c139
< 0.organizationName_default = key1088.info


0.organizationName_default = Internet Widgits Pty Ltd
151c151
< emailAddress = key1088@163.com


emailAddress = Email Address

根据密钥生存证书

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl req -new -x509 -key CA/private/cakey.pem -out CA/cacert.pem -days 1000 -config etc/openssl.cnf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:
State or Province Name (full name) [BJ]:
Locality Name (eg, city) []:
Organization Name (eg, company) [key1088.info]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:key1088
key1088@163.com []:

CA/serial3.生成客户端证书,也称为节点证书。key1088@key1088-host:/openssl-1.1.0$ (umask 077;./apps/openssl genrsa -out CA/client/c01.key 2048)Generating RSA private key, 2048 bit long modulus............+++................................................................................................+++e is 65537 (0x10001)key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl req -new -key CA/client/c01.key -out CA/client/c01.csr -config etc/openssl.cnfYou are about to be asked to enter information that will be incorporatedinto your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter '.', the field will be left blank.">创建CA服务器所需文件

key1088@key1088-host:/openssl-1.1.0$ touch CA/{index.txt,serial}
key1088@key1088-host:
/openssl-1.1.0$ ls -l CA/
总用量 8
-rw-rw-r-- 1 key1088 key1088 1176 9月 4 14:07 cacert.pem
-rw-rw-r-- 1 key1088 key1088 0 9月 4 14:09 index.txt
drwxrwxr-x 2 key1088 key1088 4096 9月 4 13:57 private
-rw-rw-r-- 1 key1088 key1088 0 9月 4 14:09 serial

证书序号开始值设置

key1088@key1088-host:/openssl-1.1.0$ echo "01" > CA/serial

3.生成客户端证书,也称为节点证书。

key1088@key1088-host:/openssl-1.1.0$ (umask 077;./apps/openssl genrsa -out CA/client/c01.key 2048)
Generating RSA private key, 2048 bit long modulus
............+++
................................................................................................+++
e is 65537 (0x10001)
key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl req -new -key CA/client/c01.key -out CA/client/c01.csr -config etc/openssl.cnf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

Country Name (2 letter code) [CN]:
State or Province Name (full name) [BJ]:
Locality Name (eg, city) []:
Organization Name (eg, company) [key1088.info]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:c01 #此处不能和CA中的名称相同
key1088@163.com []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:1234
An optional company name []:1234

4.部署到CA中

mkdir -p CA/newcerts
key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl ca -in CA/client/c01.csr -out CA/client/c01.crt -days 1000 -config etc/openssl.cnf
Using configuration from etc/openssl.cnf
Can't open ./CA/index.txt.attr for reading, No such file or directory
140399448418048:error:02001002:system library:fopen:No such file or directory:crypto/bio/bss_file.c:74:fopen('./CA/index.txt.attr','r')
140399448418048:error:2006D080:BIO routines:BIO_new_file:no such file:crypto/bio/bss_file.c:77:
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Sep  4 06:58:17 2016 GMT
            Not After : Jun  1 06:58:17 2019 GMT
        Subject:
            countryName               = CN
            stateOrProvinceName       = BJ
            organizationName          = key1088.info
            commonName                = c01
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                AB:F4:CB:4F:13:28:F8:5D:A7:DB:A0:E3:F3:52:86:C1:25:D1:EB:A0
            X509v3 Authority Key Identifier:
                keyid:68:82:F2:71:02:9C:92:00:F4:05:72:30:24:97:6B:80:2E:69:AF:EA
Certificate is to be certified until Jun  1 06:58:17 2019 GMT (1000 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
key1088@key1088-host:~/openssl-1.1.0$ du -a CA/
8	CA/newcerts/01.pem		        #自动生成的PEM文件,按照序号命名
12	CA/newcerts
4	CA/private/cakey.pem
8	CA/private
4	CA/serial
4	CA/serial.old
0	CA/index.txt.old
4	CA/cacert.pem
4	CA/index.txt
8	CA/client/c01.crt			#生成的证书
4	CA/client/c01.key
4	CA/client/c01.csr
20	CA/client
4	CA/index.txt.attr
64	CA/

openssl自带的样例:
openssl-1.1.0/apps/demoCA/下面的内容
5.转换给pfx格式(可选)

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl pkcs12 -export -clcerts -in CA/client/c01.crt -inkey CA/client/c01.key -out CA/client/c01.pfx
Enter Export Password:					#12345678
Verifying - Enter Export Password:		#同上

pfx文件包含数字签名证书和私钥,可以进行密钥转移,也可以导入到浏览器中查看。
6.验证证书是否正确

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl verify -CAfile CA/cacert.pem  CA/client/c01.crt
CA/client/c01.crt: OK

吊销证书

1.查看客户端证书序号

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl x509 -in CA/client/c01.crt -noout -serial -subject
serial=01
subject=C = CN, ST = BJ, O = key1088.info, CN = c01

2.吊销证书

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl ca -revoke CA/newcerts/01.pem -config etc/openssl.cnf
Using configuration from etc/openssl.cnf
Revoking Certificate 01.
Data Base Updated

3.生成吊销列表

key1088@key1088-host:~/openssl-1.1.0$ echo "01" > CA/crlnumber
key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl ca -gencrl -out CA/all.crl -config etc/openssl.cnf
Using configuration from etc/openssl.cnf

4.查看吊销

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl crl -in CA/all.crl -noout -text
Certificate Revocation List (CRL):
        Version 2 (0x1)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: /C=CN/ST=BJ/O=key1088.info/CN=key1088
        Last Update: Sep  4 07:37:17 2016 GMT
        Next Update: Oct  4 07:37:17 2016 GMT
        CRL extensions:
            X509v3 CRL Number:
                1
Revoked Certificates:
    Serial Number: 01
        Revocation Date: Sep  4 07:34:48 2016 GMT
    Signature Algorithm: sha256WithRSAEncryption
         a7:98:6c:8f:77:d2:52:6a:57:eb:27:75:6f:17:eb:3c:30:19:
         78:bb:4f:77:aa:94:74:28:e5:7a:05:38:8d:03:6c:7c:8f:63:
         de:a9:fd:d3:ed:77:70:7d:24:9a:b9:da:42:05:9b:d1:73:92:
         6c:0c:13:88:16:16:d7:e1:4f:6f:62:90:8a:11:b1:c5:b7:80:
         c8:6c:76:e5:d2:eb:a9:f4:ee:da:85:56:9e:f2:97:02:43:ef:
         3d:ea:77:af:5b:f1:ed:4b:39:c7:7e:9c:22:62:91:ee:17:30:
         10:d2:49:82:9a:0d:9c:0c:ad:63:1e:cb:30:33:f0:5f:55:dc:
         b6:6c:70:61:82:ba:e0:d3:98:96:f3:3d:8b:0c:02:0d:fd:ac:
         61:e5:24:46:a0:52:94:05:f4:a9:a8:8b:25:40:db:a4:e5:58:
         4f:bd:37:1d:44:87:f1:3f:f9:06:d5:47:aa:0c:8f:1e:ba:d6:
         99:ef:f7:77:e0:af:05:a3:08:78:06:0f:e2:6a:a4:cd:ac:1a:
         45:d3:18:9a:18:25:ee:c3:7b:66:51:77:52:64:5e:4a:81:9e:
         84:be:16:bc:da:7d:83:7b:49:d1:5a:0f:4f:02:e4:fa:fb:80:
         53:7f:7f:72:02:17:31:25:c6:6e:cc:1e:4f:7e:9c:f4:ae:d1:
         85:a2:6a:d6
key1088@key1088-host:~/openssl-1.1.0$ cat CA/serial
03
key1088@key1088-host:~/openssl-1.1.0$ cat CA/crlnumber
02
key1088@key1088-host:~/openssl-1.1.0$ cat CA/index.txt
R	190601065817Z	160904073448Z	01	unknown	/C=CN/ST=BJ/O=key1088.info/CN=c01
V	190601073145Z		02	unknown	/C=CN/ST=BJ/O=key1088.info/CN=c02
key1088@key1088-host:~/openssl-1.1.0$ cat CA/index.txt.old
V	190601065817Z		01	unknown	/C=CN/ST=BJ/O=key1088.info/CN=c01
V	190601073145Z		02	unknown	/C=CN/ST=BJ/O=key1088.info/CN=c02

5.验证吊销的证书和正常使用的证书

key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl  verify -crl_check -CRLfile CA/all.crl -CAfile CA/cacert.pem CA/client/c01.crt
C = CN, ST = BJ, O = key1088.info, CN = c01
error 23 at 0 depth lookup: certificate revoked
error CA/client/c01.crt: verification failed

再新生成一个02的证书验证一下,验证结果。
key1088@key1088-host:~/openssl-1.1.0$ ./apps/openssl verify -crl_check -CRLfile CA/all.crl -CAfile CA/cacert.pem CA/client/c02.crt
CA/client/c02.crt: OK
参考文章:
http://www.yunweipai.com/archives/4513.html%20r
http://raymii.org/s/articles/OpenSSL_manually_verify_a_certificate_against_a_CRL.html