DiffTT


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

Java 获取文件 MD5

发表于 2018-05-04

上周开始学习 Java 语言,作为练手项目,写了一个工具,功能就是从 S3 下载文件,再上传到另一个 S3 上,因为是大文件,为了避免重复下载上传,需要获取文件 md5 与 S3 eTag 进行对比,如果相同,就不重复下载上传。

于是我打开 Google 输入了关键词 Java md5,搜索结果主要代码如

1
File file = new File(FILE_NAME);

1
2
3
4
5
FileInputStream stream = new FileInputStream(file);
MessageDigest digest = MessageDigest.getInstance("MD5");
MappedByteBuffer byteBuffer = stream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
digest.update(byteBuffer);
byte[] hash = digest.digest();

这一段问题不大,但是要和 eTag 对比,需要转换成 32 位的 16 进制数字的字符串表达式,搜索引擎给的结果里主要有两种方式

1
2
3
4
5
6
7
8
9
10
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
if ((0xff & hash[i]) < 0x10) {
hexString.append("0" + Integer.toHexString((0xFF & hash[i])));
} else {
hexString.append(Integer.toHexString(0xFF & hash[i]));
}
}
String result = hexString.toString();

习惯写 PHP 之后,看到这类不能一个方法调用就完成的代码,有抵触心理,于是我选择了复制下面这一种实现

1
2
BigInteger bi = new BigInteger(1, hash);
String result = bi.toString(16);

问题就出现了,由于转换成 BigInteger 再输出字符串,如果第一个数字是 0 的话,就会被忽略,那么得到的结果就不是 32 位的 16 进制数字的字符串了,用这个结果和 eTag 对比就会出现对不上的情况,作为一个伪强迫症犯者,自然不能用判断长度小于 32 位就补 0 的方式。

于是我找到了 apache 开源库的代码 https://github.com/apache/commons-codec/blob/trunk/src/main/java/org/apache/commons/codec/digest/DigestUtils.java,学习人家是如何正确实现的,最后实现如下

1
2
3
4
5
6
7
8
9
10
11
12
FileInputStream stream = new FileInputStream(file);
int length = 1024;
final byte[] buffer = new byte[length];
int read = stream.read(buffer, 0, length);
MessageDigest digest = MessageDigest.getInstance(MessageDigestAlgorithms.MD5);
while (read > -1) {
digest.update(buffer, 0, read);
read = stream.read(buffer, 0, length);
}
String result = Hex.encodeHexString(digest.digest());

lnmp 一键安装环境会删除已经安装的 MySQL 问题

发表于 2018-04-23

上周同事让我把项目 demo 部署到一台内网测试机上,测试机是 CentOS 环境,而且已经有 MySQL 在运行,那我只需要安装 Nginx + PHP 即可,我为了方便想当然的执行了 lnmp 一键安装命令

1
wget -c http://soft.vpser.net/lnmp/lnmp1.4.tar.gz && tar zxf lnmp1.4.tar.gz && cd lnmp1.4 && ./install.sh lnmp

我以为在选择 MySQL 版本的时候,选择不安装即可,悲剧出现了,随后就发现测试机上的 MySQL 被卸载了,好在 MySQL 的数据还在,同事重新安装 MySQL 帮我恢复了数据。

因为安装环境期间,没有执行任何其它操作,所以基本可以断定 lnmp 安装环境导致的问题,事后我开始查看 lnmp 代码,找到了如下代码

1
2
3
4
5
6
7
8
9
10
if [ "$PM" = "yum" ]; then
CentOS_InstallNTP
CentOS_RemoveAMP
CentOS_Dependent
elif [ "$PM" = "apt" ]; then
Deb_InstallNTP
Xen_Hwcap_Setting
Deb_RemoveAMP
Deb_Dependent
fi

其中 CentOS_RemoveAMP 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CentOS_RemoveAMP()
{
Echo_Blue "[-] Yum remove packages..."
rpm -qa|grep httpd
rpm -e httpd httpd-tools --nodeps
rpm -qa|grep mysql
rpm -e mysql mysql-libs --nodeps
rpm -qa|grep php
rpm -e php-mysql php-cli php-gd php-common php --nodeps
Remove_Error_Libcurl
yum -y remove httpd*
yum -y remove mysql-server mysql mysql-libs
yum -y remove php*
yum clean all
}

也就是说 lnmp 一键安装方式,前提需要是一个完全干净的环境,否则也会被卸载,这是我没有预料到的。

Go 语言学习开篇

发表于 2017-10-13   |   分类于 Go 语言学习

最近一年走不出舒适区,个人水平有限,在技术领域也无法得到提高,整日无所事事,沉迷玩乐,感觉职业生涯走到尽头了。

也跟风学习过 Depp Learning、Data Science 等,没有数学基础,智商又不够,两三个月就放弃了,还尝试学了 iOS,仍是没坚持下来,现在轮到 Go 了,不知道能坚持多久。

先是学习了 Go 的语法,觉得其中很有意思的部分,做个记录,好督促自己这次能坚持的更久些。

类型声明与类 c 的语言不同,类型后置

1
2
3
4
5
6
7
// 变量
var i int = 1
// 函数
func add(x, y int) int {
return x + y
}

不需要 while 关键字的 while 循环

1
2
3
4
5
6
7
8
9
sum := 1
for i < 100 {
sum += i;
}
// while(true)
for {
}

switch 语句默认 break 除非使用 fallthrough

1
2
3
4
5
6
7
8
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.", os)
}

没有条件的 switch 可以替换 if-then-else

1
2
3
4
5
6
7
8
9
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}

接口的实现不需要显示声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Duck interface {
quack()
}
type BlackDuck struct {
Name string
}
func (d BlackDuck) quack() {
fmt.Printf("I am %v, is %T", d.Name, d)
}
func main() {
var d Duck
bd := BlackDuck{"Snow"}
// bd 实现了 Duck 接口定义的 quack 所以可以赋值给 d
d = bd
d.quack()
}

还有其它也非常有意思的特点,没有一一列举,入门学习主要参考官方 GO 指南

解决 Homebrew 安装软件慢的小技巧

发表于 2017-01-08

我在 macOS 上的软件安装基本优先使用 Homebrew 管理,大部分情况还好,但是一些被 GFW 屏蔽的源,就非常沮丧,有些源开了代理忍一忍就过去了,但是一些比较大的安装包,下载速度巨慢,而且容易超时,无奈只好寻找可以从本地安装的方法。

比如我在安装 gradle 的时候就很无奈,好在 Homebrew 在安装的时候会打印出下载地址

1
2
3
brew install gradle
==> Using the sandbox
==> Downloading https://downloads.gradle.org/distributions/gradle-3.3-all.zip

将下载地址复制出来,使用迅雷下载,瞬间就下载好了,那怎么样让 Homebrew 使用本地安装包呢,其实 Homebrew 在下载之前是会判断是否已经下载的,只要把文件拷贝到这个缓存目录就可以了,查看目录路径使用以下命令

1
brew --cache

在这个目录下会发现,还未完成下载的 gradle 安装包文件名为 gradle-3.3.zip.incomplete,那么将迅雷下载过的文件拷贝至这个目录,并命名为 gradle-3.3.zip,然后删除 gradle-3.3.zip.incomplete 就好了,接着再运行安装命令,就会提示已下载

1
2
3
4
brew install gradle
==> Using the sandbox
==> Downloading https://downloads.gradle.org/distributions/gradle-3.3-all.zip
Already downloaded: ...

若遇到相同问题,按照这个思路解决即可。

使用 PhpStorm + Xdebug + Chrome 进行代码调试

发表于 2017-01-06

不知道为啥,使用 IDE 进行 PHP 调试这么实用的技能,身边的 PHP 程序员(包括我)都没有掌握,大多数时候调试都依赖 var_dump printf echo 等,体验其实是非常不好的。

而我最初确实花了很长时间才搞定这套配置,说来也不算复杂,但确实耗了我很多时间,尤其是当我换电脑后想再配置的时候,竟然又花了很长时间,才配置起来,其中原因就是我自己知其然不知其所以然,不明白各个配置项的意思,故在此记录,以防下次又再耗费时间。

首先启用 Xdebug 的特性 Remote Debugging,需要在对应的 php.ini 里添加配置,当然前提是已经安装了 xdebug 扩展

1
2
3
xdebug.remote_enable=1
xdebug.remote_host="127.0.0.1" // PhpStorm 所在的 IP
xdebug.remote_port=9001 // PhpStorm 监听的端口号

依次打开 PhpStorm 的配置 Languages&Frameworks > PHP > Debug,找到 Xdebug Debug port 填写端口号为 9001,与 xdebug.remote_port 一致即可。

设置完成后,依次点击 Run > Start Listening For PHP Debug Connections,设置断点,即可开始调试。

最后配合 Xdebug Helper for Chrome 可以在 Chrome 浏览器上选择是否开启调试,不用在 PhpStorm 菜单上 Start ... > Stop ... > Start .. 繁琐操作,首先在 php.ini 添加配置

1
xdebug.idekey="PHPSTORM"

在 Xdebug Helper 的配置项里,选择 PhpStorm,确保其值与 xdebug.idekey 一致, 保存,这样只要 PhpStorm 一直开着 Start Listening ... 也可以通过浏览器扩展控制启用或禁止调试了。

PHP 在使用 Redis 中 SERIALIZER 选项时遇到的问题

发表于 2016-10-16

因为我们在很多项目中使用了

1
setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);

使用了 SERIALIZER_PHP 参数后,将会使用 serialize 函数序列化后保存到 Redis 中,于是我在使用 Sorted Sets 的时候遇到一个问题。

比如调用 zAdd 的时候,实际存储在数据库中的数据会根据类型序列化

1
2
3
zAdd('myzset', 1, 100);
zAdd('myzset', 2, 101);
zAdd('myzset', 3, "100");

会造成 100 和 “100” 实际上是两个 member,效果如

1
2
3
4
5
6
1) "i:100;"
2) "1"
3) "i:101;"
4) "2"
5) "s:3:\"100\";"
6) "3"

但是在在使用 zRange 读取数据的时候,由于反序列后的 member 是被当成同一个 key 的,所以只会读取其中一个 member,按照排序先后,后面一个的 score 会覆盖前面的 score,如

1
2
[100] => 3
[101] => 2

所以就会造成排序混乱问题

MD5 的误解

发表于 2016-08-06   |   分类于 编程中的误解
  • md5 加密
  • md5 后可以反吗?
  • md5 被破解了?

很多新手程序员对 MD5 算法产生以上的误解,主要原因都是没理解 MD5 的用处,先看看 MD5 在 Wikipedia 上的定义

MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

注意几个关键词,消息摘要算法、128位(16字节)、确保信息传输一致,那么产生以上误解的原因就是,错误的将 MD5 用于加密。

在 WEB 时代初期,用户注册后的密码以明文存储在数据库中,一旦数据库泄露,后果非常严重,于是就有人将密码 md5 后的值存在数据库中,登录时将用户输入的密码 md5 后与数据库的密码进行比对,于是一些新手程序员依葫芦画瓢,并没有理解其用意,错误的将 MD5 理解成了一种加密算法。

既然是加密算法,那就会顺理成章的想怎么解密,有部分程序员就会使用 MD5 不可逆的特性来反驳,那其实也是牛头不对马嘴,MD5 就不是加密算法,哪里会有什么解密呢。

还有一个错误的理解就是 MD5 被破解了,网上有很多现成的工具,可以根据 MD5 生成的散列值,还原出原字符串,其实大多是采用了彩虹表

彩虹表是一个用于加密散列函数逆运算的预先计算好的表, 常用于破解加密过的密码散列。

简单说就是写一个方法,将所有可能输入的原字符串 md5 后存在数据表中,这样如果散列值正好与数据库中某一条记录一致,那么就相当于还原了字符串,那么真实情况是不是还原了呢,其实未必,想想看 md5 后的散列值只有128位(16字节),真实世界中有那么多字符组合,只用 128 位来表达,想想就知道,同一个散列值可能代表很多不一样的原始字符串 md5 后的结果,也就是即使在彩虹表种找到了一致的散步列值,也不可能知道原始字符串。

而 MD5 的真正缺陷在于

1996年后被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-1。
2004年,证实MD5算法无法防止碰撞,因此无法适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。
2009年谢涛和冯登国仅用了220.96的碰撞算法复杂度,破解了MD5的碰撞抵抗,该攻击在普通计算机上运行只需要数秒钟。

现在编程中 MD5 用途最多的还是确保信息传输完整一致,也就是防篡改。

Fiddler Hosts 功能快速切换环境

发表于 2016-07-30

在工作中经常使用 Fiddler 调试,其中包括使用 Fiddler 自定义 Hosts 来完成服务端环境的切换。

1
2
3
4
5
# local
127.0.0.1 blog.difftt.com
# test
10.0.0.10 blog.difftt.com

这样切换环境的时候,需将另一组注释掉,如果一组域名有很多的话,Fiddler 的 Hosts 编辑器体验就很糟糕了,即便是编辑系统 hosts 也不是很方便。

这里就需要另一款工具 SwitchHosts,它可以将不同环境分组,实现一键切换环境,但是 Chrome 或者 Fiddler 都有自带 DNS 缓存。

好在 Fiddler 脚本里可以添加选项来设置缓存失效时间,这样只需清空 Fiddler 列表即可切换环境了。

1
2
FiddlerObject.UI.lvSessions.AddBoundColumn("Server IP", 120, "X-HostIP");
FiddlerApplication.Prefs.SetInt32Pref("fiddler.network.timeouts.dnscache", 0);

添加 IP 字段可以观察环境切换是否生效。

JoJo Zhou

JoJo Zhou

I am a PHPer

8 日志
2 分类
7 标签
© 2020 JoJo Zhou
由 Hexo 强力驱动
主题 - NexT.Pisces

Hosted by Coding Pages