什么是ONVIF
ONVIF是一个国际通用的摄像头通信控制协议,一般主流的设备厂商都支持该协议
为什么要写这篇博客
今天在实现android版本ONVIF协议时,发现它的加密方式,是一个很好的学习案例
所以特地总结一下,供大家学习,加密方式属于通用知识,不仅适用于摄像头,也适合与客户端、服务端等领域
ONVIF的通信流程
- 客户端通过Http向摄像头发送一个xml字符串,该xml中包含了随机数,用户名,当前时间,加密后的密码
- 摄像头判断随机数是否使用过,如果已使用过,直接请求失败
- 摄像头判断时间与当前差距是否很大,如果相隔很久,直接请求失败
- 摄像头通过客户端提供的信息,和真实密码,校验加密后的密码是否正确,校验失败则请求失败
- 以上流程都通过,并且内容格式正确,则请求成功
密码加密和校验原理
这里我们把摄像头当作服务端,因为它内置了一个Http服务
- 客户端将nonce+time+password相加,生成一个sha1摘要字符串,作为加密后的密码
- 服务端也按照一样的方式,生成sha1摘要,对比摘要是否相同,相同则说明密码正确
- 服务端将nonce标记为已使用,下次遇到相同的nonce,直接请求失败
- 服务端校验time和当前时间是否相隔很久,如果相隔很久,直接请求失败
- 由于sha1是摘要算法,不可逆,因此即使报文泄漏也无法抓取真实密码
- 由于nonce是一次性的,因此抓取到sha1,也无法再次复用
- 由于服务端有错误次数限制,也无法通过暴力方式破解
密码加密实现代码
package com.wp.android_onvif.onvifBean;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
public class Digest {
public String nonce;
public String username;
public String password;
public String time;
public String encryptedNonce;
public String encryptedPassword;
public static Digest build(String username, String password) throws Throwable {
Digest digest = new Digest();
digest.nonce = new Random().nextInt() + "";
digest.username = username;
digest.password = password;
digest.time = digest.getUTCTime();
digest.encryptedNonce = android.util.Base64.encodeToString(digest.nonce.getBytes(), android.util.Base64.DEFAULT).trim();
digest.encryptedPassword = digest.getEncryptedPassword().trim();
return digest;
}
protected String getUTCTime() {
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss'Z'");
format.setTimeZone(new SimpleTimeZone(SimpleTimeZone.UTC_TIME, "UTC"));
String utcTime = format.format(calendar.getTime());
return utcTime;
}
protected String getEncryptedPassword() throws Throwable {
String origin = nonce + time + password;
byte[] encrypted = sha1(origin);
String encoded = android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT);
return encoded;
}
protected byte[] sha1(String origin) throws Throwable {
MessageDigest SHA1 = MessageDigest.getInstance("SHA1");
SHA1.reset();
SHA1.update(origin.getBytes());
byte[] encrypted = SHA1.digest();
return encrypted;
}
}
|