求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
Direct Project:通过云发送医疗信息
 

作者:Dr. Michael Yuan ,发布于2013-1-29,来源:IBM

 

在几乎所有其他行业中,云计算已是推广系统可互操作性和减少 IT 成本过程中的一股革命性力量,但很难为医疗 IT 采用云计算模式。如今,云中的医疗 IT 主要仅限于托管的电子医疗记录 (EHR) 提供商的小规模应用,比如 eClinicalWorks 和 PracticeFusion(参见 参考资料)。这些提供商专注于托管数据和服务(采用软件即服务或 SaaS 模型),而不是专注于数据交换。大型医院系统目前还未以任何有意义的方式采用云计算。

云计算的潜在成本节省可能对医疗提供商是一种巨大的诱惑,使他们能够转向一种更加高效且开放的数据基础架构。能够跨医疗系统安全地共享医疗数据,这改善了患者的医疗可靠性。而且对于软件开发人员,医疗 IT 代表着一个新的创新和发现领域。

在本文中,我将介绍一个具有广阔前景的敏感数据交换协议 Direct Project,该协议由美国联邦政府开发和推广。首先概述医疗 IT 中对开放数据交换的需求和常用协议的局限性。然后介绍 Direct Project 如何填补目前为止阻碍医疗系统采用更开放的数据交换协议的安全性和基础架构空白。最后,我将提供一个使用 Direct Sender 的简单编程示例,Direct Sender 是一个实现 Direct 协议的开源、基于 Java 的客户端。

医疗 IT 与开放数据交换

医院和医生在提供对患者数据的在线访问能力上声名狼藉。甚至在 2012 年的今天,当您进入美国一家典型的医院索取您的医疗记录时,医院的工作人员也只是将它们打印出来并向您收取一定的复印费。当您的医生需要将您推荐给另一个医疗提供商时,他(或她)很可能会发送一份传真或电话联系这个提供商。这类不可靠的数据管理导致了巨大的浪费,甚至会导致医疗错误。

出于许多原因,医疗 IT 对互联网技术的采用非常缓慢。部分原因包括偏离正轨的经济刺激、高度零散的电子医疗记录 (EHR) 市场、EHR 供应商创建的供应商锁定、隐私制度,以及松散且零碎的数据交换标准。由于这些阻碍,云计算目前为止在医疗 IT 领域的收效甚微。甚至在拥有 EHR 实现的医疗提供商中,也常常是在内部托管数据,并尽量避免外部访问。

Direct Project 的发展历史

美国联邦政府最初开发医疗信息交换网络的目的是连接 DoD(国防部)和 VA(退伍军人事务部)医院,使受伤的士兵可跨医院接受更有效的治疗。该系统称为美国国家卫生信息网 (National Health Information Network, NHIN)。该系统随后由 Office of National Coordinator of Health IT (ONC) 接管并转变成一个开源项目,以用作整个美国的医疗信息交换模板。ONC 将该项目重命名为 Nationwide Health Information Network (NwHIN),随后又更名为 eHealth Exchange。

联邦 HIE 网络的核心是一个名为 CONNECT 的面向服务的架构 (SOA) 系统,该系统以 Java ESB(参见 参考资料)为基础。该网络中的医疗提供商可将他们的系统插入总线中。CONNECT 网络可以一种分层方式进行组织。但是在实际中,这样一个系统的维护和扩展非常复杂。

认识到 CONNECT 的局限性后,ONC 接下来开发了一个名为 Direct 的缩小版的 HIE 基础架构。不同于 CONNECT,Direct 设计为对等结构。习惯于通过传真来回传送信息的医疗提供商对 Direct 的开放、对等的数据交换模型很熟悉,因此 Direct 更可能得到广泛采用。

电子邮件安全和医疗 IT

存储在医疗 IT 系统中的患者数据必须遵守严格的隐私和安全法规,比如健康保险流通与责任法案 (HIPAA) 和医疗信息技术促进经济和临床健康法案 (HITECH)。这些法规相结合,定义了数据应该如何存储和传输,以及谁应对数据泄露负责。

电子邮件是通过互联网交换文档过程中最广泛使用的工具。尽管电子邮件无处不在,但它对发送医疗记录和转诊信息等敏感数据而言还不够安全。电子邮件地址可能被仿冒,电子邮件内容在到达收件人的收件箱之前会以明文形式通过互联网上的多个第三方邮件服务器。这些事实与 HIPAA 和 HITECH 安全规则相抵触。

但自在金融行业中首次发现以来,事实证明电子邮件的普遍性具有很大的吸引力。在医疗 IT 诞生以前很久,金融行业就抢先开发了安全电子邮件来与在线银行用户共享敏感的财务信息。IETF RFC 3851(安全/多用途的互联网邮件扩展 (S/MIME) 消息规范)定义了一种使用公钥基础架构 (PKI) 协议加密电子邮件附件的方式。根据 RFC 3851 的规定,S/MIME 附件可包含一封完整的电子邮件,该邮件然后通过公共互联网发送。

尽管 PKI 是一个广泛接受的互联网安全标准,但使用基于 PKI 的安全电子邮件还是具有一些重大的阻碍:

PKI 需要邮件发件人和收件人都拥有一个既定的颁发机构颁发的数字证书。该数字证书为一封共享电子邮件提供了加密和解密密钥。更重要的是,它建立了发件人和收件人的身份,以便没有任何人能仿冒该邮件。获取一个人的证书常常是一个漫长且代价很高的过程。

一旦发件人和收件人收到了他们的数字证书,他们就需要将这些证书公布在互联网上,因为发件人和收件人必须在可彼此发送电子邮件之前交换证书。这个问题已通过创建一个将电子邮件证书存储在 DNS 服务器上的新 DNS 标准而得到了部分解决,DNS 服务器是可公开发现的。但互联网上的大部分 DNS 服务都没有实现此功能。

发件人和收件人都必须使用一个支持 S/MIME 加密和 DNS 证书发现的电子邮件客户端。

这些阻碍结合在一起,极大地阻碍了用户对安全电子邮件的采用。尽管如此,在医疗提供商与患者之间的安全文档交换的具体用例方面,Direct 协议还是一项不错的解决方案。

医疗 IT 中的用例

在医疗行业,一种流行的文档交换模式是提供商彼此之间交换患者信息。所交换的文档包括转诊信息、医疗历史、保险信息、测试结果、药物列表等。目前,医疗提供商通过传真发送这些文档,但通过安全电子邮件发送它们会更有意义。

医疗 IT 中的另一种常见用例是,医疗提供商直接将记录发送给患者。这里,Direct Project 借鉴了金融行业操作手册中的一页内容。Direct 协议不是将电子邮件直接发送给患者,而是支持我们为医疗系统中的每位患者创建一个电子邮件帐户,然后在一封安全电子邮件到达时向患者的常用帐户发送一封通知邮件。患者然后需要登录到医疗系统的患者门户,通过一个类似 Web 邮件的界面查阅其安全邮件。在此情况下,安全电子邮件将在医疗组织的内部电子邮件系统内发送。

使用 Direct 发送安全电子邮件

您已大体了解了安全电子邮件和 Direct Project 的背景,希望您可看到在医疗 IT 系统中实现 Direct 协议和基础架构的好处。让我们看看如何设置该基础架构来发送安全电子邮件。为此,您需要一个能够接收 Direct 电子邮件的目标电子邮件地址。幸运的是,我们可以使用 Microsoft HealthVault(参见 参考资料),它为所有人都提供了 Direct 支持。首先在 Microsoft HealthVault 开发服务器 http://direct.healthvault-stage.com 上创建一个帐户。创建帐户后,您将有一个启用了 Direct 的安全电子邮件地址 your_id@healthvault-stage.com。您可通过向该地址发送电子邮件来测试您的代码。

接下来,您将需要创建一个公钥数字证书,您将使用该证书对电子邮件进行数字签名。公钥数字证书可向邮件收件人确保所发送的邮件确实来自您。您的每个发件人地址都需要一个数字证书。一种选择是从 Verisign 等证书颁发机构获取一个受信任的数字证书,并将其放在您的 DNS 记录中供收件人发现。HealthVault 大大简化了这一过程,支持您只需创建一个自签名的证书,然后即可通过电子邮件将它发送给 hvbd@microsoft.com。HealthVault 团队将该证书加载为受信任证书。自签名证书使得开发人员使用 Direct 系统做试验变得容易得多。

使用一个工具(比如 Apple Keychain),很容易生成一个私钥/公钥对和数字证书。清单 1 展示了来自常用的 openssl 工具(几乎在所有常见操作系统中都有提供)的命令。

清单 1. 使用 openssl 创建一个公钥/私钥对

openssl req -new -newkey rsa:2048 -nodes -keyout private.key
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out public.crt
openssl pkcs12 -export -in public.crt -inkey private.key -out bundle.p12 

清单 1 中的第一个命令创建了一个私钥,您可使用该私钥对消息进行签名和加密。第二个命令创建了一个公钥数字证书,供收件人解密和验证您的消息。第三个命令创建了私钥和公钥对的一个 PKCS12 组件 (bundle),您可在应用程序中方便地使用它。您需要为组件中的私钥输入一个名称和密码。

通过电子邮件将 public.crt 文件发送给 Microsoft HealthVault,这将告知系统信任由您的私钥签名的消息。

Direct Sender

Direct Sender 是我创建的一个开源库,以便更容易地发送 Direct 邮件。Direct Sender 托管在 GitHub 之上,我在 GitHub 上还提供了一个快速入门教程。该库支持通过多种方式发送电子邮件,从使用您自行托管的 SMTP 服务器到 Gmail 服务器,再到专业的事务型电子邮件服务,比如 Amazon Web Service Simple Email Service 和 SendGrid。使用该库,只需两行代码即可通过 Gmail 发送一封安全电子邮件,如 清单 2 所示:

清单 2. 通过 Gmail 发送安全电子邮件

Client client = new GmailClient (
                username, password,
                "/bundle.p12", keyname, keypass);

client.sendMessage(from, to, subject, body); 

在 清单 2 中,username 和 password 是指用于发送安全电子邮件的 Gmail 帐户的凭据。bundle.p12 是在 清单 1 中创建的 PKCS12 文件。请确保此文件位于您的应用程序的类路径中。keyname 和 keypass 是组件中的私钥的名称和密码。

发送一封安全邮件后,您应收到从 HealthVault 发送的一封通知邮件,提醒您的收件箱中有一封新邮件。登录到 HealthVault 查看该邮件。

Direct Sender 的幕后操作

您已了解了如何使用 Direct Sender 库。但在以兼容 Direct 的格式加密和发送消息时,幕后发生了什么?一些开源 Java 库完成了 Direct Sender 的主要工作:

Bouncy Castle 是 Java Cryptography Extension(JCE)的一个开源实现,为在 Java 平台上实现 PKI 支持提供了必要的工具。它还提供了 S/MIME 加密实用程序等加载项。

javamail.crypto 支持将 S/MIME 附件合并到 JavaMail Transport API 中。

dnsjava 是一个基于 Java 的 DNS 客户端,能够在数字证书中查询存储在一个 DNS 记录中的电子邮件地址。

Java Client 抽象类支持签名、加密和创建 S/MIME 电子邮件等基本功能。请注意,在 清单 3 中,我首先使用一个私钥对消息进行了签名,然后使用收件人的公钥证书加密该消息。当收件人收到消息后,HealthVault 将首先使用收件人地址的私钥对该消息进行解密,然后使用发件人的公钥(假设它已通过电子邮件发送给 HealthVault)验证发件人的身份。

清单 3. 客户端处理消息签名和加密

public abstract class Client {
    
    protected String p12KeyStore;
    protected String priKeyName;
    protected String priKeyPass;
    
    public void sendMessage (String from, String to, String subject, String body) 
      throws Exception {
        byte [] pubKeyCer = lookupCertificate (to);
        if (pubKeyCer == null || pubKeyCer.length == 0) {
            throw new Exception ("Cannot find public key certificate for " + to);
        }
        
        Session session = createSession ();
        
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress(from));
        msg.setSender(new InternetAddress(from));
        msg.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to));
        msg.setSubject(subject);
        msg.setText(body);
        
        msg = signMsg(session, msg);
        msg = encryptMsg(session, msg, pubKeyCer);
        msg.saveChanges();
        
        transport(session, msg);
    }
    
    public void sendMessage (String from, String to, String subject, Multipart multipart) 
      throws Exception {
        byte [] pubKeyCer = lookupCertificate (to);
        if (pubKeyCer == null || pubKeyCer.length == 0) {
            throw new Exception ("Cannot find public key certificate for " + to);
        }
        
        Session session = createSession ();
        
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress(from));
        msg.setSender(new InternetAddress(from));
        msg.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to));
        msg.setSubject(subject);
        msg.setContent(multipart);
        
        msg = signMsg(session, msg);
        msg = encryptMsg(session, msg, pubKeyCer);
        msg.saveChanges();
        
        transport(session, msg);
    }
    
    protected byte [] lookupCertificate (String email) {
        try {
            String domain = email.replaceAll("@", ".");
            
            CERTRecord cx = null;
            Record [] records = new Lookup(domain, Type.CERT).run();
            for (int i = 0; i < records.length; i++) {
                cx = (CERTRecord) records[i];
            }
            
            if (cx == null) {
                return null;
            } else {
                return cx.getCert ();
            }
            
        } catch (Exception e) {
            e.printStackTrace ();
            return null;
        }
    }
    
    protected MimeMessage encryptMsg(Session session, MimeMessage msg, byte [] pubKeyCer) 
      throws Exception {
        EncryptionUtils encUtils = EncryptionManager.getEncryptionUtils
          (EncryptionManager.SMIME);
        
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate)cf.generateCertificate
          (new ByteArrayInputStream(pubKeyCer));
       
        // wrap certificate in BouncySMIMEEncryptionKey 
        BouncySMIMEEncryptionKey smimekey = new BouncySMIMEEncryptionKey();
        smimekey.setCertificate(cert);

        return encUtils.encryptMessage(session, msg, smimekey);
    }
    
    protected MimeMessage signMsg(Session session, MimeMessage mimeMessage) throws 
    Exception {
        // Getting of the S/MIME EncryptionUtilities.
        EncryptionUtils encUtils = EncryptionManager.getEncryptionUtils
          (EncryptionManager.SMIME);

        // Loading of the S/MIME keystore from the file (stored as resource).
        char[] keystorePass = priKeyPass.toCharArray();
        EncryptionKeyManager encKeyManager = encUtils.createKeyManager();
        encKeyManager.loadPrivateKeystore(Client.class.getResourceAsStream(p12KeyStore), 
          keystorePass);

        // Getting of the S/MIME private key for signing.
        Key privateKey = encKeyManager.getPrivateKey(priKeyName, keystorePass);

        // Signing the message.
        return encUtils.signMessage(session, mimeMessage, privateKey);
    }
    
    protected abstract Session createSession ();
    protected abstract void transport (Session session, MimeMessage msg) throws Exception;
    
} 

接下来,GmailClient 类(如清单 4 中所示)实现 Java createSession() 和 transport() 方法,这些方法提供了通过 Gmail 服务器发送电子邮件的能力:

清单 4. 通过 Gmail 发送安全电子邮件

public class GmailClient extends Client {

    String username, password;
    
    public GmailClient (String username, String password, String p12KeyStore,
     String priKeyName, 
      String priKeyPass) {
        super.p12KeyStore = p12KeyStore;
        super.priKeyName = priKeyName;
        super.priKeyPass = priKeyPass;
        
        this.username = username;
        this.password = password;
    }
    
    protected Session createSession() {
        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.socketFactory.port", "465");
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.port", "465");
 
        return Session.getDefaultInstance(props,
                new javax.mail.Authenticator() {
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(username, password);
                    }
                }
        );
    }

    protected void transport(Session session, MimeMessage msg) throws Exception {
        Transport transport = session.getTransport("smtp");
        transport.connect();
        Transport.send(msg);
        transport.close();
    }
       
} 

接收 Direct 电子邮件

使用 Direct 协议发送安全电子邮件,并使用 Direct Sender 客户端,使得医生的办公室能够轻松且安全地将转诊信息和测试结果发送给医院和患者。我们也可使用它使移动医疗 (mHealth) 应用程序能够将从患者收集的数据发送到他(或她)的数字医疗记录中。对于许多医疗应用程序来说,发送 Direct 邮件的能力是惟一的需求。在很少的情形中,您的应用程序可能需要能够接收安全电子邮件。

如果没有广泛的业务和技术审核,大部分大型医院 EHR 不会外发患者记录,所以小型医疗 IT 系统很少会收到安全电子邮件。从另一个电子医疗记录系统接收安全电子邮件也比只是发送电子邮件更加复杂。要从一个医院 EHR 接收医疗记录,您很可能使用一种标准技术(比如 HL7 (Health Level Seven International) 框架)建立一个直接的 XML-over-HTTPS 连接(参见 参考资料)。

如果您需要开发一个内部基础架构来从另一个医疗提供商接收 Direct 电子邮件,那么您将需要至少完成下列的一件事:

与将要发送您的数据的医疗提供商创建一个业务关联协议。这是一个漫长的过程,而且会为双方都规定责任。

设置一个电子邮件基础架构,以便可以自动解密和验证传入电子邮件,然后将这些邮件提供给防火墙后的收件人。

第一个需求是一个业务需求,第二个需求是技术需求。为了帮助 IT 管理员设置他们自己的 Direct 电子邮件基础架构,Direct Project 支持能够处理 Direct 消息的电子邮件网关服务器的开源参考实现。提供了针对 Java EE、Microsoft .Net 和 PHP 框架的参考实现(参见 参考资料)。

结束语

借助来自联邦政府和许多医疗系统的支持,Direct Project 凭借其优厚的条件成为交换简单医疗文档(比如转诊信息、保险信息、测试结果和医疗历史)的事实标准。Direct Project 构建于现有的、流行的电子邮件基础架构之上,但它的采用需要医疗提供商更新他们的 IT 系统来支持安全电子邮件标准。作为从事医疗 IT 工作的软件开发人员,您很快就会发现 Direct 协议是一个安全地发送电子医疗记录的不可或缺的工具。


 
分享到
 
 


专家视角看IT与架构
软件架构设计
面向服务体系架构和业务组件的思考
人人网移动开发架构
架构腐化之谜
谈平台即服务PaaS
更多...   
相关培训课程

云计算原理与应用
Windows Azure 云计算应用