40

如何向我的 C# 桌面应用程序添加许可证?我需要找到一种合适的免费方法来防止未经授权的用户安装我的软件。

4

6 回答 6

78

我可能有点晚了,但我花了一些时间试图找出一种快速有效的方法来保护一个小 C# 应用程序,我想分享我的结果。

看来您可以相当容易地使用 RSA 构建自己的、相当安全的许可系统。

显然,在保护软件方面没有什么是万无一失的(这就像保护你的房子免受窃贼:警报、吠叫的狗和栅栏使它变得比它的价值更麻烦,但它们不会阻止决心进入的人)

所以,让它变得比它的价值更麻烦是软件保护中的关键短语:如果你提供一个价值 1,000,000 美元的 ERP 系统,你会希望获得通过网络服务授权的非常好的保护(并且用户为系统支付了那么多钱)允许该系统持续访问互联网不会有问题)

但是,如果您只为一个小应用程序收取 5 至 30 美元的费用,那么用户将不会忍受非常繁重的授权。

我认为最简单的系统是对包含产品详细信息、用户及其持续时间的许可证文件进行数字签名。

这意味着对许可证文件的任何修改都会使数字签名无效。

可以使用 SignData 方法从 DSACryptoServiceProvider 类中获取数字签名。

需要私钥来签署数据,并且该密钥的公共部分可用于验证签名:(因此应用程序必须可以访问公钥)

DSAXCryptoServiceProvider 具有创建和使用密钥的方法:

DSACryptoServiceProvider.ToXMLString(bool includePrivate);

以 XML 字符串的形式返回当前在服务提供者中的公钥或公钥和私钥。

DSACryptoServiceProvider.FromXMLString(String xmlString)

此方法使用从 DSACryptoServiceProvider.ToXMLString() 获得的现有私钥或公钥设置新的 DSACryptoServiceProvider

该系统安全性的唯一缺陷是用户可能会闯入提供自己的公钥。这将允许他们从自己的私钥生成自己的许可证文件。

这可以通过额外签署应用程序所需的资源(如包含应用程序基本逻辑的 .dll,甚至 .exe 本身)来解决 - 因此,如果更改公钥,这个附加(隐藏)签名将失效。

改善这一点的其他方法包括模糊许可条款(使用二进制格式化程序将包含许可条款的数据结构序列化为字节数组,然后使用 Convert.ToBase64String() 将非常有效地模糊许可条款,即使用户能够替换他们仍然需要计算数据表示的公钥)

我有一个我写的示例系统,但它太大而无法完全引用,但这是其中的 CreateLicense 方法:

    /// <summary>
    /// use a private key to generate a secure license file. the private key must match the public key accessible to
    /// the system validating the license.
    /// </summary>
    /// <param name="start">applicable start date for the license file.</param>
    /// <param name="end">applicable end date for the license file</param>
    /// <param name="productName">applicable product name</param>
    /// <param name="userName">user-name</param>
    /// <param name="privateKey">the private key (in XML form)</param>
    /// <returns>secure, public license, validated with the public part of the key</returns>
    public static License CreateLicense(DateTime start, DateTime end, String productName, String userName, String privateKey)
    {
        // create the licence terms:
        LicenseTerms terms = new LicenseTerms()
        {
            StartDate = start,
            EndDate = end,
            ProductName = productName,
            UserName = userName
        };

        // create the crypto-service provider:
        DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();

        // setup the dsa from the private key:
        dsa.FromXmlString(privateKey);

        // get the byte-array of the licence terms:
        byte[] license = terms.GetLicenseData();

        // get the signature:
        byte[] signature = dsa.SignData(license);

        // now create the license object:
        return new License()
        {
            LicenseTerms = Convert.ToBase64String(license),
            Signature = Convert.ToBase64String(signature)
        };
    }

验证方法:

    /// <summary>
    /// validate license file and return the license terms.
    /// </summary>
    /// <param name="license"></param>
    /// <param name="publicKey"></param>
    /// <returns></returns>
    internal static LicenseTerms GetValidTerms(License license, String publicKey)
    {
        // create the crypto-service provider:
        DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();

        // setup the provider from the public key:
        dsa.FromXmlString(publicKey);

        // get the license terms data:
        byte[] terms = Convert.FromBase64String(license.LicenseTerms);

        // get the signature data:
        byte[] signature = Convert.FromBase64String(license.Signature);

        // verify that the license-terms match the signature data
        if (dsa.VerifyData(terms, signature))
            return LicenseTerms.FromString(license.LicenseTerms);
        else
            throw new SecurityException("Signature Not Verified!");
    }

许可条款类:

    /// <summary>
    /// terms of the license agreement: it's not encrypted (but is obscured)
    /// </summary>
    [Serializable]
    internal class LicenseTerms
    {
        /// <summary>
        /// start date of the license agreement.
        /// </summary>
        public DateTime StartDate { get; set; }

        /// <summary>
        /// registered user name for the license agreement.
        /// </summary>
        public String UserName { get; set; }

        /// <summary>
        /// the assembly name of the product that is licensed.
        /// </summary>
        public String ProductName { get; set; }

        /// <summary>
        /// the last date on which the software can be used on this license.
        /// </summary>
        public DateTime EndDate { get; set; }

        /// <summary>
        /// returns the license terms as an obscure (not human readable) string.
        /// </summary>
        /// <returns></returns>
        public String GetLicenseString()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                // create a binary formatter:
                BinaryFormatter bnfmt = new BinaryFormatter();

                // serialize the data to the memory-steam;
                bnfmt.Serialize(ms, this);

                // return a base64 string representation of the binary data:
                return Convert.ToBase64String(ms.GetBuffer());

            }
        }

        /// <summary>
        /// returns a binary representation of the license terms.
        /// </summary>
        /// <returns></returns>
        public byte[] GetLicenseData()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                // create a binary formatter:
                BinaryFormatter bnfmt = new BinaryFormatter();

                // serialize the data to the memory-steam;
                bnfmt.Serialize(ms, this);

                // return a base64 string representation of the binary data:
                return ms.GetBuffer();

            }
        }

        /// <summary>
        /// create a new license-terms object from a string-representation of the binary
        /// serialization of the licence-terms.
        /// </summary>
        /// <param name="licenseTerms"></param>
        /// <returns></returns>
        internal static LicenseTerms FromString(String licenseTerms)
        {

            using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(licenseTerms)))
            {
                // create a binary formatter:
                BinaryFormatter bnfmt = new BinaryFormatter();

                // serialize the data to the memory-steam;
                object value = bnfmt.Deserialize(ms);

                if (value is LicenseTerms)
                    return (LicenseTerms)value;
                else
                    throw new ApplicationException("Invalid Type!");

            }
        }

    }
于 2011-05-30T00:58:34.683 回答
15

.NET 有很多许可证管理系统(甚至还有一个内置的许可证控制系统)。快速搜索“.NET 许可证管理器”的 Google 推出了免费的开放许可证系统。

我希望您可以轻松找到更多信息。

于 2010-09-02T06:08:30.867 回答
13

我认为值得为此添加另一个答案,因为接受的答案似乎引用了当前未维护的项目。

我建议查看Standard.Licensing,这是一个免费的开源许可库,适用于 .Net 框架、Mono、.Net Core、.Net Standard 和 Xamarin。通过添加对较新平台(特别是 .Net Core 和 .Net Standard)的支持,对旧的Portable.Licensing进行了现代化改造。

Standard.Licensing 通过创建一个数字签名的 XML 文件来工作,该文件包含与您的产品相关的信息,例如产品类型和到期日期。当您检查许可证时,可以验证 XML 文件未被更改的事实,然后您的应用程序可以信任许可证文件中的声明。(请注意,您可能还需要验证计算机的时钟是否准确,以防止有人只是更改日期。)

Standard.Licensing 使用椭圆曲线数字签名算法 (ECDSA)算法对 XML 文件进行签名,该算法在创建许可文件时使用一对密钥,一个公钥和一个私钥。您只需要使用公钥来解密和验证许可证。由于不可能只使用公钥来修改许可证文件,因此您可以安全地将公众包含在您的应用程序中,而无需采取诸如混淆程序集之类的方法来防止人们看到公钥。请注意,这类似于上面 Simon Bridge 的回答中提到的方法。

Standard.Licensing 有一个流畅的 API,可用于创建和验证许可证。这是他们网站上的片段,展示了如何创建许可证:

var license = License.New()  
    .WithUniqueIdentifier(Guid.NewGuid())  
    .As(LicenseType.Trial)  
    .ExpiresAt(DateTime.Now.AddDays(30))  
    .WithMaximumUtilization(5)  
    .WithProductFeatures(new Dictionary<string, string>  
        {  
            {"Sales Module", "yes"},  
            {"Purchase Module", "yes"},  
            {"Maximum Transactions", "10000"}  
        })  
    .LicensedTo("John Doe", "john.doe@example.com")  
    .CreateAndSignWithPrivateKey(privateKey, passPhrase);

然后在您的应用程序中加载并验证许可文件:

using Standard.Licensing.Validation;

var license = License.Load(...);
var validationFailures = license.Validate()  
                                .ExpirationDate()  
                                .When(lic => lic.Type == LicenseType.Trial)  
                                .And()  
                                .Signature(publicKey)  
                                .AssertValidLicense();
于 2019-12-15T10:02:15.840 回答
2

从技术上讲,创建有效且安全的许可系统并非易事。如果您打算开发商业软件,我建议您使用一些商业解决方案。自定义编码的许可系统往往容易受到攻击。

我对Treek 的许可库有最好的体验。即使对于单个开发人员来说,它也很便宜,它安全且具有良好的支持。在比较成本时,TLL 比租用自己的开发人员来做同样的工作要便宜。

此外,您需要保护您的资源。为此,我们确实使用EAZ Fuscator,但也有免费选项可用。EAZ 很好,但是很贵。

于 2019-02-03T14:40:08.617 回答
0

一种方法是推出您自己的部分密钥验证系统。Code Project 上有一个 VB.NET 版本:

http://www.codeproject.com/KB/security/cdkeys.aspx

于 2010-09-02T06:16:10.200 回答
0

注意你为保护你的应用付出了多少努力;如果它很容易被破坏,或者如果它非常强大(例如每次运行时都必须输入密码),则可能会浪费时间。

一篇关于(游戏的)软件保护的有趣文章可以在这里找到: http ://www.positech.co.uk/talkingtopirates.html

于 2010-09-02T06:26:11.923 回答