22

我想构建一个桌面应用程序并能够发布产品密钥或序列号。在用户可以使用该应用程序之前,他将被要求输入产品密钥/序列号。

当他们提供 XXXX-XXXX-XXXX-XXXX 之类的键时,类似于 Microsoft Office

我的想法是根据许可证销售应用程序并为每台设备提供产品密钥似乎比帐户(用户名和密码)更专业。

所以我的问题是:

1)是否有可能做到这一点electron

2)你能告诉我我应该去获取序列号(如果可行的话)还是账户?还是有更好的选择?

3)如果你回答了第二个问题。请说明为什么?

4

4 回答 4

22

2021 年编辑:我想修改这个答案,因为它对我在许可证密钥和用户帐户之间进行的比较产生了很多询问。以前我几乎总是建议使用用户帐户来授权 Electron 应用程序,但后来我改变了自己的立场,变得更加细致入微。对于大多数 Electron 应用程序,许可证密钥就可以了。

向 Electron 应用程序添加许可证密钥(与产品密钥同义)验证非常简单。首先,您希望以某种方式为每个用户生成一个许可证密钥。这可以使用密码学来完成,也可以通过生成“随机”许可证密钥字符串并将其存储在数据库中,然后构建可以验证给定许可证密钥是否“有效”的 CRUD 许可服务器来完成。

对于加密许可证密钥,您可以从客户那里获取一些信息,例如他们的订单号或电子邮件地址,并使用 RSA 加密创建它的“签名”。使用 Node,看起来像这样:

const crypto = require('crypto')

// Generate a new keypair
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
  // Using a larger key size, such as 2048, would be more secure
  // but will result in longer signatures.
  modulusLength: 512,
  privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
  publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
})

// Some data we're going to use to generate a license key from
const data = 'user@store.example'

// Create a RSA signer
const signer = crypto.createSign('rsa-sha256')
signer.update(data)

// Encode the original data
const encoded = Buffer.from(data).toString('base64')

// Generate a signature for the data
const signature = signer.sign(privateKey, 'hex')

// Combine the encoded data and signature to create a license key
const licenseKey = `${encoded}.${signature}`

console.log({ privateKey, publicKey, licenseKey })

然后,为了验证您的 Electron 应用程序中的许可证密钥,您需要通过将上面生成的公共(而不是私有!)密钥嵌入到您的应用程序代码库中来加密“验证”密钥的真实性:

// Split the license key's data and the signature
const [encoded, signature] = licenseKey.split('.')
const data = Buffer.from(encoded, 'base64').toString()

// Create an RSA verifier
const verifier = crypto.createVerify('rsa-sha256')
verifier.update(data)

// Verify the signature for the data using the public key
const valid = verifier.verify(publicKey, signature, 'hex')

console.log({ valid, data })

像这样生成和验证加密签名许可证密钥的真实性将非常适合许多简单的许可需求。它们相对简单,离线工作也很好,但有时验证许可证密钥是否“有效”是不够的。有时要求规定许可证密钥不是永久的(即永远“有效”),或者他们需要更复杂的许可系统,例如一次只有有限数量的设备(或座位)可以使用应用程序的许可系统。或者许可证密钥可能需要可更新的到期日期。这就是许可证服务器可以发挥作用的地方。

许可证服务器可以帮助管理许可证的激活、到期等,例如用于将多个许可证或功能许可证与单个用户或团队相关联的用户帐户。我不推荐用户帐户,除非您对它们有特定需求,例如您需要额外的用户配置文件信息,或者您需要将多个许可证与单个用户相关联。

但是,如果您不是特别热衷于编写和维护自己的内部许可系统,或者您只是不想像上面那样编写自己的许可证密钥生成器,我是软件的创始人许可 API 称为Keygen,它可以帮助您快速启动和运行,而无需编写和托管您自己的许可服务器。:)

Keygen 是一个典型的 HTTP JSON API 服务(即没有软件需要你的应用程序打包)。它可以在任何编程语言中使用,也可以与 Electron 等框架一起使用。

在最简单的形式中,使用 Keygen 验证许可证密钥就像点击单个 JSON API 端点一样简单(随意在终端中运行它):

curl -X POST https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key \
  -d '{
        "meta": {
          "key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
        }
      }'

我最近整理了一个向 Electron 应用程序添加许可证密钥验证以及设备激活和管理的示例。您可以在 GitHub 上查看该存储库:https ://github.com/keygen-sh/example-electron-license-activation 。

我希望这能回答您的问题并为您提供一些见解。很高兴回答您的任何其他问题,因为我现在已经为 Electron 应用程序实施了几次许可。:)

于 2017-07-05T17:59:51.620 回答
4
  1. 是的,但关于软件注册机制,这很困难,也需要大量测试。
  2. 如果您的 90% 的用户可以访问互联网,那么您绝对应该使用用户帐户或一些 OAUTH 2.0 即插即用的东西(使用 facebook/gmail/whatever 登录)
  3. 我使用加密和 fs 模块从头开始构建了一个软件许可架构,这是一段相当长的旅程(一年)!

不建议从头开始为你的软件做一个好的注册机制,electron 比较难,因为源代码比较暴露。

话虽这么说,如果你真的想那样做,bcrypt 擅长这个(散列),你需要一个唯一的用户标识符来散列,你还需要某种持久性(最好是一个文件),你可以在其中存储用户许可证,并且您需要通过散列散列来隐藏用于散列的盐......或将其中的一小部分存储在单独的文件中。

这将为许可提供一个良好的起点,但远未得到充分保障。

希望能帮助到你 !

于 2017-06-22T22:09:13.733 回答
2

有许多服务可以帮助您将基于许可证密钥的软件许可添加到您的应用程序。为了确保您的客户不会重复使用密钥,您需要一个强大的设备指纹识别算法。

你可以试试Cryptlex。它提供了一个非常强大的许可解决方案,具有先进的设备指纹算法。您可以查看Github上的Node.js示例,为您的电子应用程序添加许可。

于 2019-05-04T07:48:45.450 回答
1

是的,这可能的。

我自己想要这个功能,我找到了相关的解决方案,例如付费视频教程、在线解决方案 [使用Keygen ] 和其他随机黑客,但我想要离线免费的东西,所以我为自己/其他人创建了自己的存储库利用。这是它的工作原理。

概述

  1. 安装secure-electron-license-keys-cli. (即。npm i -g secure-electron-license-keys-cli)。
  2. 通过运行创建许可证密钥secure-electron-license-keys-cli。这会生成public.key,private.keylicense.data
  3. 保持private.key安全,但要坚持在您的 Electron 应用程序public.keylicense.data根目录中。
  4. 安装secure-electron-license-keys. (即。npm i secure-electron-license-keys)。
  5. 在您的main.js文件中,查看此示例代码并添加必要的绑定。
const {
    app,
    BrowserWindow,
    ipcMain,
} = require("electron");
const SecureElectronLicenseKeys = require("secure-electron-license-keys");
const path = require("path");
const fs = require("fs");
const crypto = require("crypto");

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;

async function createWindow() {

    // Create the browser window.
    win = new BrowserWindow({
        width: 800,
        height: 600,
        title: "App title",
        webPreferences: {
            preload: path.join(
                __dirname,
                "preload.js"
            )
        },
    });

    // Setup bindings for offline license verification
    SecureElectronLicenseKeys.mainBindings(ipcMain, win, fs, crypto, {
        root: process.cwd(),
        version: app.getVersion(),
    });

    // Load app
    win.loadURL("index.html");

    // Emitted when the window is closed.
    win.on("closed", () => {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        win = null;
    });
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);

// Quit when all windows are closed.
app.on("window-all-closed", () => {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== "darwin") {
        app.quit();
    } else {
        SecureElectronLicenseKeys.clearMainBindings(ipcMain);
    }
});
  1. 在您的preload.js文件中,查看示例代码并添加支持代码。
const {
    contextBridge,
    ipcRenderer
} = require("electron");
const SecureElectronLicenseKeys = require("secure-electron-license-keys");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld("api", {
    licenseKeys: SecureElectronLicenseKeys.preloadBindings(ipcRenderer)
});
  1. 查看示例React组件如何验证许可证的有效性,并在您的应用程序中采取相应措施。
import React from "react";
import {
  validateLicenseRequest,
  validateLicenseResponse,
} from "secure-electron-license-keys";

class Component extends React.Component {
  constructor(props) {
    super(props);

    this.checkLicense = this.checkLicense.bind(this);
  }

  componentWillUnmount() {
    window.api.licenseKeys.clearRendererBindings();
  }

  componentDidMount() {
    // Set up binding to listen when the license key is
    // validated by the main process
    const _ = this;

    window.api.licenseKeys.onReceive(validateLicenseResponse, function (data) {
      console.log("License response:");
      console.log(data);
    });
  }

  // Fire event to check the validity of our license
  checkLicense(event) {
    window.api.licenseKeys.send(validateLicenseRequest);
  }

  render() {
    return (
      <div>
        <button onClick={this.checkLicense}>Check license</button>
      </div>
    );
  }
}

export default Component;

你完成了!

更多细节

为了进一步解释,许可证由来自客户端(即前端)页面的请求进行验证。客户端通过此调用()向主(即后端)进程发送IPC 请求window.api.licenseKeys.send(validateLicenseRequest)

一旦后端进程接收到这个调用(因为我们用这个调用 () 设置了它,所以它被连接起来了SecureElectronLicenseKeys.mainBindings),库代码会尝试license.datapublic.key. 无论这是否成功,成功状态都会被发送回客户端页面(通过 IPC)。

如何按版本限制许可证密钥

我所解释的内容非常有限,因为它不限制您可能提供给特定用户的应用程序版本。secure-electron-license-keys-cli包括您在生成许可证密钥以设置许可证的特定主要/次要/补丁/过期值时可能传递的标志。

如果您想允许最高 7 的主要版本,您可以运行命令来生成许可证文件,如下所示: secure-electron-license-keys-cli --major "7"

如果您想允许最高 7 的主要版本并在 2022-12-31 到期,您可以运行以下命令生成许可证文件,如下所示: secure-electron-license-keys-cli --major "7" --expire "2022-12-31"

如果您确实运行了这些命令,则需要更新您的客户端页面以便与它们进行比较,即:

window.api.licenseKeys.onReceive(validateLicenseResponse, function (data) {

    // If the license key/data is valid
    if (data.success) {

        if (data.appVersion.major <= data.major &&
            new Date() <= Date.parse(data.expire)) {

            // User is able to use app
        } else {
            // License has expired
        }
    } else {
        // License isn't valid
    }
});

存储库页面有更多选项的详细信息,但这应该为您提供您将要做的事情的要点。

限制

这并不完美,但可能会处理 90% 的用户。这不能防止:

  • 有人反编译您的应用程序并制作自己的许可证以完全使用/删除许可证代码
  • 有人复制许可证并将其交给另一个人

如果您要打包多个或自动化的 .exe,还需要考虑如何运行这个库,因为这些许可证文件需要包含在源代码中。我会把它留给你的创造力来解决。

额外资源/免责声明

我构建了这个问题中提到的所有secure-electron-secure-electron-template * 存储库,如果您需要交钥匙,我还维护其中已经预先烘焙到解决方案中的许可证密钥设置。

于 2021-04-22T04:57:09.480 回答