drm是数字版权保护的一种方式,前一段时间在做四川文轩数字图书馆项目的时候用到了相关的知识,感觉这东西对于一些在线阅读和视频播放还是有很大用处的。
对于其工作原理我也很好奇,先摘抄度娘的内容如下,当然你也可以直接访问度娘:http://baike.baidu.com/view/47310.htm?fr=aladdin
DRM技术的工作原理是,首先建立数字节目授权中心。编码压缩后的数字节目内容,可以利用密钥(Key)进行加密保护(lock),加密的数字节目头部存放着KeyID和节目授权中心的URL。用户在点播时,根据节目头部的KeyID和URL信息,就可以通过数字节目授权中心的验证授权后送出相关的密钥解密(unlock),节目方可播放。
需要保护的节目被加密,即使被用户下载保存,没有得到数字节目授权中心的验证授权也无法播放,从而严密地保护了节目的版权。
密钥一般有两把,一把公钥(public key),一把私钥(private key)。公钥用于加密节目内容本身,私钥用于解密节目,私钥还可以防止当节目头部有被改动或破坏的情况,利用密钥就可以判断出来,从而阻止节目被非法使用。 上述这种加密的方法,有一个明显的缺陷,就是当解密的密钥在发送给用户时,一旦被黑客获得密钥,即可方便解密节目,从而不能真正确保节目内容提供商的实际版权利益。另一种更加安全的加密方法是使用三把密钥,即把密钥分成两把,一把存放在用户的Pc机上,另一把放在验证站(access ticket)。要解密数字节目,必须同时具备这两把密钥,方能解开数字节目。
毫无疑问,加密保护技术在开发电子商务系统中正起着重要的防盗版作用。比如,在互联网上传输音乐或视频节目等内容,这些内容很容易被拷贝复制。为了避免这些风险,节目内容在互联网上传输过程中一般都要经过加密保护。也就是说,收到加密的数字节目的人必须有一把密钥(key)才能打开数字节目并播放收看。因此,传送密钥的工作必须紧跟在加密节目传输之后。
市场上比较多应用的是微软的 DRM 技术。
系统原理
系统会将密钥标识和许可证颁发机构地址写入打包加密后的内容的头部,并且使用另一对密钥。
通过椭圆曲线加密算法对头部信息进行签名,防止头部信息被非法修改。
这个密钥散列的前12字节将用作生成加密过程中使用的密钥。
这12个字节将作为一个密钥,通过RC4算法加密一个全零的64字节串,得到一个64字节的加密结果。
具体运算过程是,32位数据与6个32位字的的密钥的第一节字节相乘,然后除以4294967296,取其余数,并交换结果的高16位和低16位,然后再与6个32位字的的密钥的第二节字节相乘,然后除以4294967296取其余数,并交换结果的高16位和低16位,然后再与6个32位字的的密钥的第三节字节相乘,然后除以4294967296取其余数,并交换结果的高16位和低16位,然后再与6个字节的密钥的第四节字节相乘,然后除以4294967296取其余数,并交换结果的高16位和低16位,然后再与6个32位字的的密钥的第一节字节相乘,然后除以4294967296取其余数,并交换结果的高16位和低16位,然后再加上6个32位字的密钥,然后除以4294967296取其余数,上述过程定义为函数f(x),设一个64位状态值,并设初值为零。
说了太多的废话,这玩意儿到底怎么用还没说,下面来点干货,上代码
/**
* 文件名 BaseFoac.java
* 包含类名列表 com.issmobile.numlibrary.tool
* 版本信息 版本号
* 创建日期 2014年7月15日
* 版权声明
*/ package com.issmobile.numlibrary.tool; import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames; import android.util.Log; import com.foxit.general.BufferFileRead;
import com.foxit.general.DrmNative;
import com.foxit.general.PdfBaseDef;
import com.foxit.general.PdfDocNative;
import com.foxit.general.PdfDrmNative;
import com.foxit.general.ObjectRef;
import com.foxit.general.PdfSecurityNative;
import com.foxit.general.RsaKey;
import com.foxit.general.RtBaseDef;
import com.foxit.general.RtNative; /**
* 类名
* @author 王洪贺<br/>
* 实现的主要功能。
* 创建日期 2014年7月15日
*/ public class BaseFoac { protected ObjectRef m_encryptParams = null;
private CookieStore m_cookieStore = null;
private byte[] m_decoderPubKey = null; /**用户名*/
private String mUserName;
/**密码*/
private String mPassword;
/**密码*/
private String mDevSN; private String m_host = null;
private String m_port = null;
private String m_object = null;
private ObjectRef security = null;
private RsaKey m_rsaKey = null;
/**文件本地地址*/
private String drmfile = null;
private DefaultHttpClient httpClient = null;//new DefaultHttpClient();
private String m_sessionID = null; /**
* 获取信封的时候初始化,需要用户名密码
* */
public BaseFoac(String mUserName, String mPassword, String drmfile) {
this.mUserName = "ElibUser." + mUserName;
this.mPassword = mPassword;
this.mDevSN = mPassword;
this.drmfile = drmfile;
} /**
* 解密文件的时候初始化
* */
public BaseFoac() {
} public void setDRMFileName(String filename) {
drmfile = filename;
} public boolean isDocWrapper(ObjectRef document) {
if (!PdfDrmNative.isDocWrapper(document))
return false;
return true;
} public boolean isFoxitDRM(ObjectRef document) {
if (!PdfDrmNative.isDocWrapper(document))
return false; m_encryptParams = PdfDrmNative.getEncryptParams(document); if (m_encryptParams == null)
return false; return true;
} protected String getServiceURL() {
//TODO 获取到书籍中验证drm的网址,由于目前服务器不可用,返回默认地址,正式版修改回来
// return PdfDrmNative.getEncryptParamsItemString(m_encryptParams,
// PdfBaseDef.ENCRYPTPARAMS_SERVICEURL);
return URLConstants.licenseURL;
} /**
* 解析网址
*/
protected void parseURL(String serviceUrl) {
if (serviceUrl == null)
return;
int hostIndex = serviceUrl.indexOf("://");
String tmp = serviceUrl.substring(hostIndex + 3);
int objIndex = tmp.indexOf("/");
int portIndex = tmp.indexOf(":");
if (portIndex < 0) {
m_port = "80";
m_host = tmp.substring(0, objIndex);
} else {
m_port = tmp.substring(portIndex + 1, objIndex);
m_host = tmp.substring(0, portIndex);
}
m_object = tmp.substring(objIndex + 1);
} /**
* 获取开始的请求信息
*/
protected String getSessionBeginRequest(String sessionID) {
ObjectRef foac = DrmNative.createFoac(true);
if (foac == null)
return null; DrmNative.setFoacSessionID(foac, sessionID); ObjectRef category = DrmNative.getFoacDataCategory(foac);
if (category == null) {
DrmNative.deleteFoac(foac);
return null;
} DrmNative.setFoacRequestID(foac, "SessionBegin"); ObjectRef subCategory = DrmNative.addSubCategory(category, "FlowCode", true);
String flowCode = PdfDrmNative.getEncryptParamsItemString(m_encryptParams,
PdfBaseDef.ENCRYPTPARAMS_FLOWCODE);
DrmNative.setCategoryAttribute(subCategory, "Value", flowCode); String request = DrmNative.saveFoac(foac);
DrmNative.deleteFoac(foac);
return "XmlContent=" + request;
} /**
* 发送请求信息并返回接受到的数据,需访问网络
*/
protected String sendAndReceive(String bsSend) {
String result = null;
try {
httpClient = new DefaultHttpClient();
String temp = bsSend.replace("&", "%26");
temp = temp.replace("+", "%2B");
InetAddress addr = InetAddress.getByName(m_host); String url;
if (addr.toString().substring(1).indexOf("/") != -1)
url = "http://" + m_host + ":" + m_port + "/" + m_object;
else
url = "http://" + m_host + ":" + m_port + "/" + m_object;
HttpPost httpPost = new HttpPost(url);
HttpEntity entity = new StringEntity(temp);
httpPost.setEntity(entity); httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); // if (m_cookieStore != null)
httpClient.setCookieStore(m_cookieStore); httpClient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 300000);
httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 300000); HttpResponse httpResponse = httpClient.execute(httpPost);
int status = httpResponse.getStatusLine().getStatusCode();
long len = httpResponse.getEntity().getContentLength(); StringBuilder builder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(httpResponse
.getEntity().getContent()));
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
builder.append(s);
}
result = builder.toString();
m_cookieStore = ((AbstractHttpClient) httpClient).getCookieStore(); if (result.indexOf("xml") == -1)
return null; } catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 解析收到的信息
*/
protected void parseSessionBeginRecieve(String receive) {
ObjectRef foac = DrmNative.loadFoac(receive.getBytes());
if (foac == null)
return; String state = DrmNative.getFoacAnswerState(foac);
if (state.indexOf("1") > -1) {
ObjectRef category = DrmNative.getFoacDataCategory(foac); //Pubkey
int iCount = DrmNative.countSubCategories(category, "ServerPubKey");
if (iCount == 0) {
DrmNative.deleteFoac(foac);
return;
}
ObjectRef subCategory = DrmNative.getSubCategory(category, "ServerPubKey", 0);
String bsPubKey = DrmNative.getCategoryAttributeValue(subCategory, "Value"); ObjectRef sessionID = DrmNative.getSubCategory(category, "SessionID", 0);
m_sessionID = DrmNative.getCategoryAttributeValue(sessionID, "Value"); // m_decoderPubKey = Base64.decode(bsPubKey, Base64.DEFAULT);
m_decoderPubKey = RtNative.base64Decode(bsPubKey.getBytes(), 0, bsPubKey.length());
}
DrmNative.deleteFoac(foac);
} /**
* 加密字符串
*/
protected String EncryptString(String str) {
byte[] data = DrmNative.pkiRsaEncrypt(str, m_decoderPubKey);
//return Base64.encodeToString(data, Base64.DEFAULT);
return RtNative.base64EncodeToString(data, 0, data.length);
} /**
* 获取检查账户的请求信息
*/
protected String GetCheckAccountRequest(String sessionID) {
String fileID = PdfDrmNative.getEncryptParamsItemString(m_encryptParams,
PdfBaseDef.ENCRYPTPARAMS_FILEID);
String strFileID = EncryptString(fileID); String order = PdfDrmNative.getEncryptParamsItemString(m_encryptParams,
PdfBaseDef.ENCRYPTPARAMS_ORDER);
String strOrder = EncryptString(order); String account = EncryptString(mUserName);
String password = EncryptString(mPassword); ObjectRef foac = DrmNative.createFoac(true);
DrmNative.setFoacSessionID(foac, sessionID); DrmNative.setFoacRequestID(foac, "AuthAccount"); ObjectRef category = DrmNative.getFoacDataCategory(foac);
if (category == null) {
DrmNative.deleteFoac(foac);
return null;
} ObjectRef subCategory = DrmNative.addSubCategory(category, "EncryptAccount", true);
DrmNative.setCategoryAttribute(subCategory, "Value", account); subCategory = DrmNative.addSubCategory(category, "EncryptPassword", true);
DrmNative.setCategoryAttribute(subCategory, "Value", password); subCategory = DrmNative.addSubCategory(category, "EncryptOrderID", true);
DrmNative.setCategoryAttribute(subCategory, "Value", strOrder); subCategory = DrmNative.addSubCategory(category, "EncryptFileID", true);
DrmNative.setCategoryAttribute(subCategory, "Value", strFileID); String request = DrmNative.saveFoac(foac); DrmNative.deleteFoac(foac); return "XmlContent=" + request;
} /**
* 解析收到账户认证的信息
*/
protected boolean ParseCheckAccountRequest(String receive) {
ObjectRef foac = DrmNative.loadFoac(receive.getBytes());
if (foac == null)
return false; boolean bRet = false; ///foac verify
String bsState = DrmNative.getFoacAnswerState(foac);
if (bsState.equals("1")) {
ObjectRef category = DrmNative.getFoacDataCategory(foac); int iCount = DrmNative.countSubCategories(category, "Result");
if (iCount == 0) {
DrmNative.deleteFoac(foac);
return false;
}
ObjectRef subCategory = DrmNative.getSubCategory(category, "Result", 0);
String result = DrmNative.getCategoryAttributeValue(subCategory, "Value");
bRet = result.equals("1");
}
DrmNative.deleteFoac(foac);
return bRet;
} /**
* 获取信封请求信息
*/
public String GetEnvelopRequest(String sessionID) {
String fileID = PdfDrmNative.getEncryptParamsItemString(m_encryptParams,
PdfBaseDef.ENCRYPTPARAMS_FILEID);
String strFileID = EncryptString(fileID); String order = PdfDrmNative.getEncryptParamsItemString(m_encryptParams,
PdfBaseDef.ENCRYPTPARAMS_ORDER);
String strOrder = order;//EncryptString(order); String userName = mUserName;//EncryptString(m_userName);
String password = EncryptString(mPassword);
String devSn = EncryptString(mDevSN); byte[] seed = {
'F', 'o', 'x', 'i', 't', 'A', 'n', 'd', 'r', 'o', 'i', 'd'
}; m_rsaKey = DrmNative.createRsaKey(1024, seed, null); if (m_rsaKey == null)
return null; String strClientPubKey = RtNative.base64EncodeToString(m_rsaKey.publicKey, 0,
m_rsaKey.publicKey.length);
// String strClientPubKey = Base64.encodeToString(clientPubKey, Base64.DEFAULT); ObjectRef foac = DrmNative.createFoac(true);
DrmNative.setFoacSessionID(foac, sessionID); DrmNative.setFoacRequestID(foac, "GetEnvelop"); ObjectRef category = DrmNative.getFoacDataCategory(foac);
if (category == null) {
DrmNative.deleteFoac(foac);
return null;
} ObjectRef subCategory = DrmNative.addSubCategory(category, "OrderID", true);
DrmNative.setCategoryAttribute(subCategory, "Value", strOrder); subCategory = DrmNative.addSubCategory(category, "EncryptFileID", true);
DrmNative.setCategoryAttribute(subCategory, "Value", strFileID); subCategory = DrmNative.addSubCategory(category, "ClientPubKey", true);
DrmNative.setCategoryAttribute(subCategory, "Value", strClientPubKey); subCategory = DrmNative.addSubCategory(category, "Usermail", true);
DrmNative.setCategoryAttribute(subCategory, "Value", userName); subCategory = DrmNative.addSubCategory(category, "EncryptPassword", true);
DrmNative.setCategoryAttribute(subCategory, "Value", password); subCategory = DrmNative.addSubCategory(category, "EncryptDeviceSN", true);
DrmNative.setCategoryAttribute(subCategory, "Value", devSn); String request = DrmNative.saveFoac(foac); DrmNative.deleteFoac(foac); return "XmlContent=" + request;
} /**
* 解析收到的信封信息
*/
public boolean parseEnvelopRequest(ObjectRef document, String receive) { BufferFileRead bufReader = new BufferFileRead(receive.getBytes(), 0);
ObjectRef envelope = DrmNative.loadEnvelope(bufReader);
byte[] key = DrmNative.getEnvelopeKey(envelope); String algorithm = DrmNative.getEnvelopeAlgorithm(envelope);
byte[] deKey = DrmNative.pkiRsaDecrypt(key, m_rsaKey.privateKey); security = new ObjectRef();
String filter = "FoxitSTD";
int ret = PdfSecurityNative.createFoxitDRMSecurity(filter,
algorithm.equals("FOXIT_ENCRYPT2") ? RtBaseDef.CIPHER_AES : RtBaseDef.CIPHER_RC4,
deKey, security);
if (ret != RtBaseDef.ERR_SUCCESS)
return false; int offset = PdfDrmNative.getDocWrapperOffset(document);
boolean flag = PdfSecurityNative.verifyFoxitDRMSecurity(security); ret = PdfDocNative.closeDoc(document);
if (ret != RtBaseDef.ERR_SUCCESS)
return false; ret = PdfDocNative.loadDoc(drmfile, 0, offset, document);
if (ret != RtBaseDef.ERR_SUCCESS)
return false; return true;
} /**
* 解密文件
*/
public boolean decrypt(ObjectRef document) {
if (document == null)
return false; String serviceUrl = getServiceURL();
if (serviceUrl == null)
return false;
parseURL(serviceUrl);
System.out.println("URL ==" + serviceUrl); String sessionID = "6F9629FF-8A86-D011-B42D-00C04FC964FF";
String send = getSessionBeginRequest(sessionID);
if (send == null)
return false;
String receive = sendAndReceive(send);
if (receive == null)
return false;
parseSessionBeginRecieve(receive); String checkAccount = GetCheckAccountRequest(sessionID);
if (checkAccount == null)
return false;
receive = sendAndReceive(checkAccount);
if (receive == null)
return false;
if (!ParseCheckAccountRequest(receive))
return false; String envelope = GetEnvelopRequest(sessionID);
if (envelope == null)
return false;
receive = sendAndReceive(envelope);
if (receive == null)
return false; return parseEnvelopRequest(document, receive);
} /**
* 获取信封
* @author honghe
*/
public String getEnvelop(ObjectRef document) {
if (document == null)
return null; String serviceUrl = getServiceURL();
if (serviceUrl == null)
return null;
parseURL(serviceUrl);
System.out.println("URL ==" + serviceUrl); String sessionID = "6F9629FF-8A86-D011-B42D-00C04FC964FF";
String send = getSessionBeginRequest(sessionID);
if (send == null)
return null;
String receive = sendAndReceive(send);
if (receive == null)
return null;
parseSessionBeginRecieve(receive); String checkAccount = GetCheckAccountRequest(sessionID);
if (checkAccount == null)
return null;
receive = sendAndReceive(checkAccount);
if (receive == null)
return null;
if (!ParseCheckAccountRequest(receive))
return null; String envelope = GetEnvelopRequest(sessionID);
if (envelope == null)
return null;
receive = sendAndReceive(envelope);
if (receive == null)
return null; return receive;
} /**
* 根据信封信息解密文档
* @author honghe
*/
public boolean decryptDoc(ObjectRef document, String receive) {
BufferFileRead bufReader = new BufferFileRead(receive.getBytes(), 0);
ObjectRef envelope = DrmNative.loadEnvelope(bufReader);
byte[] key = DrmNative.getEnvelopeKey(envelope);
String algorithm = DrmNative.getEnvelopeAlgorithm(envelope);
byte[] seed = {
'F', 'o', 'x', 'i', 't', 'A', 'n', 'd', 'r', 'o', 'i', 'd'
};
m_rsaKey = DrmNative.createRsaKey(1024, seed, null);
byte[] deKey = DrmNative.pkiRsaDecrypt(key, m_rsaKey.privateKey); security = new ObjectRef();
String filter = "FoxitSTD";
int ret = PdfSecurityNative.createFoxitDRMSecurity(filter,
algorithm.equals("FOXIT_ENCRYPT2") ? RtBaseDef.CIPHER_AES : RtBaseDef.CIPHER_RC4,
deKey, security);
if (ret != RtBaseDef.ERR_SUCCESS)
return false; int offset = PdfDrmNative.getDocWrapperOffset(document);
boolean flag = PdfSecurityNative.verifyFoxitDRMSecurity(security); ret = PdfDocNative.closeDoc(document);
if (ret != RtBaseDef.ERR_SUCCESS)
return false; ret = PdfDocNative.loadDoc(drmfile, 0, offset, document);
if (ret != RtBaseDef.ERR_SUCCESS)
return false; return true;
} public void destroy() {
int ret = RtBaseDef.ERR_ERROR;
if (m_encryptParams != null)
ret = PdfDrmNative.releaseEncryptParams(m_encryptParams);
if (security != null)
ret = PdfSecurityNative.destroySecurity(security); } }
简单的说一下调用的过程:
1.首先用客户端下载一本经过drm加密的书籍(加密的过程是文轩那边已经加密好的了)
2.下载完后根据书籍中的drm地址和加密信息以及用户的用户名和密码获取信封(按照福昕提供的api是在阅读的时候在线联网解密的,但客户要求可以离线阅读,因此下载书的时候要先取得解密用的信封)
获取信封的调用方法
if(finished) {
FoxitRAMManager.getInstance();
ObjectRef document = new ObjectRef();
int result = PdfDocNative.loadDoc(book.loc, null, document);
if (result != RtBaseDef.ERR_SUCCESS) {
throw new Exception("load drm fail!");
}
User user = AppContext.getInstance().getUserModel().user;
BaseFoac baseFoac = new BaseFoac(user.uName, user.pwd, book.loc);
if (baseFoac.isFoxitDRM(document)) {
baseFoac.setDRMFileName(book.loc);
String envelop = baseFoac.getEnvelop(document);
if (envelop != null) {
book.receive = envelop;
}
else {
throw new Exception("get envelop fail!");
}
}
// baseFoac.destroy();
PdfDocNative.closeDoc(document);
}
3.将获取的信封根据书籍对应的信息保存到数据库中,一本书一个用来解密的信封。
4.用户打开书籍的时候获取该书籍存储于数据库中的信封,用信封对书籍进行解密,用户就可以看到该书籍了。
FoxitRAMManager.getInstance();
document = new ObjectRef();
int result = PdfDocNative.loadDoc(filePath, null, document);
if (result != RtBaseDef.ERR_SUCCESS)
return false;
// 根据得到的信封解密书籍
mBaseFoac = new BaseFoac();
if (mBaseFoac.isDocWrapper(document)) {
mBaseFoac.setDRMFileName(filePath);
if (!mBaseFoac.decryptDoc(document, receive)) {
return false;
}
} else {
return false;
}
这样做的好处是该用户下载的书籍用其他的阅读器是无法打开的,而且解密的信封也是跟用户和服务器相关的,其他人或者是不联网验证也是无法查看传送的书籍的,有效的保护了数字版权。
代码已上传github。
地址为:https://github.com/dongweiq/study/tree/master/pdf_drm
代码中appid和password已删除,此外你只有加入jar包和so文件才可以运行。
我的github地址:https://github.com/dongweiq/study
欢迎关注,欢迎star o(∩_∩)o 。有什么问题请邮箱联系 dongweiqmail@gmail.com qq714094450