65.9K
CodeProject 正在变化。 阅读更多。
Home

解密 Crystal Reports 中的数据

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (21投票s)

2003年12月28日

CPOL

6分钟阅读

viewsIcon

170546

downloadIcon

2144

如何使用公式为 Crystal Reports 解密数据,通过用户函数库自动化您的托管代码。

引言

我在此必须强调,本文绝非旨在作为 Crystal Reports 教程、Interop、安全或密码学课程,但它是一个非常具体的概述,说明如何使用托管代码编写自定义函数或重用现有托管代码库中的投资,以帮助促进 SQL Server 数据库中数据的解密以供 Crystal Reports 使用。但是,我将简要说明一些对于经验丰富的 Crystal 用户来说显而易见的事情,所以如果您是其中之一,请耐心等待。

什么是用户函数库?

用户函数库 (UFL) 是用户定义函数,通过 COM 接口向程序员开放,使用 Crystal Reports 的公式编辑器。程序员可以在不打开公式编辑器的情况下编写 Crystal Reports,但那些使用过 Crystal Reports 报表功能的人都会对这个工具的力量和实用性有所体会。简而言之,公式编辑器在字段级别为我们提供了编程控制、自定义格式、派生计算的能力,并将熟悉的编程结构(如决策、迭代求值)置于我们的指尖。顺便说一句,您可以以两种语法编写针对公式编辑器的代码,即 Crystal 和 Basic(大多数 VB 程序员可能会因为显而易见的原因直接选择 Basic)。

图 1.0

上图演示了我们正在使用一个用户函数库,并且公式编辑器,就我们的目的而言,是一个非常有用的代码编辑器,它附带了一组标准的模板化函数,附加函数(UFL 存在的地方)和一组完整的现成模板化运算符。在这里,我创建了一个公式字段,它使用数据库字段fldTestEncrypted(来自表tblTestEncrypt)作为 UFL rijDecryptorReconstruct 的参数。一旦用这行简短的琐碎代码创建了这个公式,我就能够将其放置在报表中(可能在报表的“详细信息”部分),并且对于报表打印的每一行,此字段都将传递给一个方法,该方法将解密数据库中的数据并以可读格式显示字段的明文值,如图 2.0 和 3.0 所示。

图 2.0 (Crystal Report Viewer 中解密的数据)

报表中的第 1 行和第 2 行在数据库中是什么样的?见下文。

图 3.0 (SQL Server 中的加密数据)

UFL 是公开一个或多个函数的动态链接库,这些函数是 COM 自动化服务器的一部分。当您安装 Crystal Reports for .NET 时,文件 U2lcom.dll 会安装到您的安装目录的 Crystal Decisions\1.0\Bin\ 文件夹中。U2lcom.dll 是一个 UFL,它能够利用为多种目的自定义编写的 COM DLL 文件。这里的注意事项是,这些自动化服务器必须使用 COM 兼容语言构建(根据 Crystal Decisions 的说法,不能是用于构建注册用于互操作的 DLL 文件的 .NET 语言),并且您必须根据 Crystal 定义的标准命名约定命名您的 DLL。我们很快就会看到如何做到这一点。

利用 System.Security.Cryptography

因为我们希望利用 FCL 密码学库来加密数据,所以我们需要使用托管代码解密相同的数据。为了本文的目的,我选择使用 C# 和 RijndaelManaged 密码学库来创建程序集,以加密和解密我的数据。

清单 1.0(托管代码加密和解密)

using System; 
using System.Security.Cryptography; 
using System.Text; using System.IO;


namespace Dotnetstudios 
{ 
    public class CrystalEncryption 
    { 
      private static MemoryStream memStream = new MemoryStream(); 
      private string b64key = "06W9tLEMdwxKv1C6XMoel7ibhix9dPpwJqdZ+7EuMMU="; 
      private string b64iv = "dIOh4fhx5iL6JzGw6b2B1yp56jxShQqwo2U2G3GqI3s="; 
      private ASCIIEncoding textConverter = new ASCIIEncoding(); 
      private RijndaelManaged myRijndael = new RijndaelManaged();
      public CrystalEncryption(){}  


      public string CrystalEncrypt(string encData) 
      {  
        try 
        {        
            byte[] rijKey = Convert.FromBase64String(b64key);  
            byte[] rijIv = Convert.FromBase64String(b64iv);  
            myRijndael.BlockSize = 256;  
            myRijndael.KeySize = 256;  
            ICryptoTransform encryptor = 
                     myRijndael.CreateEncryptor(rijKey, rijIv);  
            MemoryStream msEncrypt = new MemoryStream();  
            CryptoStream csEncrypt = new CryptoStream(msEncrypt, 
                     encryptor, CryptoStreamMode.Write);  
            byte[] toEncrypt;  
            toEncrypt = textConverter.GetBytes(encData);  
            csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);  
            csEncrypt.FlushFinalBlock();  
            byte[] encrypted;  
            encrypted = msEncrypt.ToArray();  
            string cipherText = Convert.ToBase64String(encrypted, 
                     0 , encrypted.Length);  
            return cipherText; 
        } 
        catch(Exception ex) 
        { 
                     throw ex; 
        } 
      } 
      public string CrystalDecrypt(string decData) 
      { 
        try 
        { 
            byte[] rijKey = Convert.FromBase64String(b64key); 
            byte[] rijIv = Convert.FromBase64String(b64iv); 
            byte[] encrypted; 
            encrypted = Convert.FromBase64String(decData); 
            myRijndael.BlockSize = 256; 
            myRijndael.KeySize = 256; 
            ICryptoTransform decryptor = 
              myRijndael.CreateDecryptor(rijKey, rijIv); 
            MemoryStream msDecrypt = new MemoryStream(encrypted); 
            CryptoStream csDecrypt = new CryptoStream(msDecrypt, 
              decryptor, CryptoStreamMode.Read); 
            byte[] fromEncrypt; 
            fromEncrypt = new byte[encrypted.Length]; 
            csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length); 
            string roundtrip = textConverter.GetString(fromEncrypt); 
            return roundtrip; 
        } 
        catch(Exception ex) 
        { 
            return ex.Message;  
        } 
      } 
    } 
}

为什么需要互操作?

很简单,上面这个类并不是实际的 UFL(请记住,UFL 只能在支持 COM 的环境中编写,不包括为互操作注册的 .NET 程序集)。因此,这里需要一个用 VB 或 C++(甚至可能是 Delphi——不要引用我的话)构建的自动化服务器。我们将使用 COM 自动化服务器来创建一个 UFL,它是一个用于执行加密和解密的托管代码库的包装器。如果您正在使用 Visual Studio .NET,那么您需要做的就是将项目属性表中的注册 COM 互操作属性设置为 true 并构建项目,即可注册托管代码程序集。

图 4.0

如果您不使用 VS.NET,那么 Regasm 工具也能很好地完成工作。在典型安装中,您可以在 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 找到它。

构建 UFL

我将此处的示例限制为 Visual Basic,因为 C++ 不是我的强项。首先创建一个 Visual Basic ActiveX DLL 项目,并添加您希望由 UFL 公开的函数以及任何必要的私有函数。您希望在 Crystal 环境中可用的所有函数都必须声明为 PUBLIC,并且必须只返回以下数据类型之一:IntegerLongSingleDoubleCurrencyDateBooleanString。您不能使用 Variant 类型。

将 ActiveX DLL 项目名称更改为 CRUFLxxx,其中 xxx 可以是任意三个字符,且第一个字符必须是字母。CRUFLa87 和 CRUFLrij 都可以正常工作。项目名称的第一部分必须是 CRUFL,以便 U2lcom.dll 能够理解您的 UFL 中定义的 PUBLIC 函数。

图 5.0 (Visual Studio 6 IDE)

列表 2.0

Public Function Reconstruct(data As String) As String 
  Dim reco
  Set reco = CreateObject("Dotnetstudios.CrystalEncryption") 
  Reconstruct = reco.CrystalDecrypt(data) 
End Function

上述代码使用后期绑定来创建我们托管程序集类的实例。CreateObject 函数中使用的字符串参数必须是完全限定的,即 namespace.namespace.namespace.Class Name

最后,构建 ActiveX DLL,将其放置在与 U2lcom.dll 相同的目录中,然后使用 Regsvr32.exe 注册您的 DLL。如果您不熟悉 Regsver32.exe,则可以使用命令窗口或 Windows 中的 运行 对话框来执行如下命令:regsvr32.exe "C:\Program Files\Common Files\Crystal Decisions\1.0\Bin\CRUFLrij.dll"。这将向 Windows 注册 ActiveX DLL。

在 Crystal Reports 中使用您的 UFL

现在剩下的就是创建一个公式字段,在您的 Crystal 报表中调用 UFL。首先创建一个报表,然后使用字段资源管理器,创建一个新建...公式字段。在公式编辑器中函数树的附加函数节点下,选择您的 UFL 和相应的函数,并选择相关的数据库字段作为参数传入。

图 6.0 (创建新公式)

图 7.0 (编写公式)

测试公式,保存,然后运行报表,您应该会看到加密数据被解密并以明文形式显示,如图 2.0所示。

结语

显然,应该考虑 Crystal Reports 的安全问题,否则,如果您打算通过报表将数据暴露给任何窥探者,那么加密数据似乎毫无意义。正如我之前所述,本文不打算讨论保护托管代码的问题。但我强烈建议您确保您的报表是安全的,否则您的数据将面临风险。

最后,我要感谢 Ido Millet 教授的帮助和指导。

© . All rights reserved.