安全(加密)困境

时间:2009-05-07 16:49:06

标签: c# .net wpf database encryption

我有一个访问数据库的内部WPF客户端应用程序。

应用程序是支持团队的中心资源,因此包括客户端的远程访问/登录信息。目前这个数据库不能通过网络界面等获得,但有一天很可能。

远程访问信息包括客户端网络的用户名和密码,以便我们可以远程支持客户的软件应用程序。我需要将用户名和密码存储在数据库中,并为支持顾问提供访问权限,以便他们可以登录客户端系统然后提供支持。希望这是有道理的。

所以困境是我不想在数据库中以明文形式存储用户名和密码,以确保如果数据库遭到破坏,我就不会向任何获取数据库的人提供访问我们客户端网络的权限。

我已经看过密码的双向加密,但正如他们所说,双向与明文没有太大的不同,就好像你可以解密它一样,攻击者也是如此......最终。这里的问题是我已经设置了一个方法来使用存储在应用程序中的salt和密码,我使用了存储在db中的salt,但都有它们的弱点,即如果应用程序被反映它暴露了盐等。

如何保护我的数据库中的用户名和密码,但仍然能够让我的支持顾问查看应用程序中的信息,以便他们可以使用它来登录?

这显然与存储用户密码不同,因为这是一种方式,因为我不需要知道它们是什么。但我确实需要知道客户端的远程访问密码是什么,因为我们需要在远程访问密码时输入密码。

有人对这里最好的方法有一些理论吗?

更新 我正在尝试构建的功能是我们的CRM应用程序,它将存储客户端的远程访问详细信息。 CRM系统提供呼叫/问题跟踪功能,在调查问题的过程中,支持顾问需要远程访问。然后,他们将查看客户端的远程访问详细信息并建立连接

7 个答案:

答案 0 :(得分:6)

有几种方法可以做到这一点;最佳解决方案将取决于您的支持团队如何访问客户的站点,有多少成员属于支持团队,以及您的应用程序的体系结构。

执行此类操作的最佳方法是使用Kerberos之类的东西。这样,支持团队的成员就不必被委托给客户的密码 - 他们可以写下来的密码,以后用来攻击客户。支持团队可以立即撤销成员的访问权限,而无需客户采取任何行动。

但是,我猜这是一个更危险的系统,团队成员通过远程桌面,SSH或其他类似方式获取密码来访问客户端系统。在这种情况下,当向团队成员透露客户密码时,会承担很大的责任。

就个人而言,我不会接受这种风险。并不是说我不相信我的团队,更重要的是我不能相信我的客户。如果在他们的网站上发生了某些事情(或者即使他们只是假装发生了某些事情),突然之间我就是一个嫌疑人。我宁愿设计一个系统,没有人可以访问单独行动的客户系统。这可以保护客户免受我团队中的坏苹果的侵害,并保护我免受诬告。

无论如何,一种方法是为每个团队成员生成密钥。这些可能是基于密码的对称加密密钥,但是必须集中保存一些密钥。更好的是使用像RSA这样的非对称算法。然后只集中保存团队成员的公钥。

当从客户端收到新密码时,使用需要副本的每个团队成员的公钥对其进行加密。此加密密码可以存储在数据库中,并在每次请求时提供给团队成员,也可以主动推送给团队成员以供他们存储。在任何一种情况下,当需要时,密码将使用团队成员持有的私钥进行解密(在使用他们选择的密码加密的密钥存储区中)。

这有不利之处。重申上述观点,团队成员可以访问客户端密码。他们值得信任吗?如果客户端存在与此系统无关的安全漏洞,但又想将责任归咎于某人,该怎么办?其次,虽然服务器上没有存储解密密钥,但仍需要为每个团队成员的公钥建立信任。否则,攻击者可能会将自己的流氓公钥放入集合中并接收可以解密的密码。

答案 1 :(得分:4)

类似的情况发生在我们公司,数据库管理员希望在他们自己之间维护一个凭证池。

我原本打算发布这个想法,但是erickson beat me to it。但是,发布一些伪代码可能值得一提,所以我想我回答这个问题的时间并没有完全浪费......

您需要的东西:

首先,让我们设置数据库架构。这些表格将很快演示。

CREATE TABLE users (
  user_id               INTEGER,
  authentication_hash   BINARY,
  authentication_salt   BINARY,
  public_key            BINARY,
  encrypted_private_key BINARY,
  decryption_key_salt   BINARY,
  PRIMARY KEY(user_id)
)

CREATE TABLE secrets (
    secret_id INTEGER,
    -- WHATEVER COLUMNS YOU REQUIRE TO ACCURATELY MODEL YOUR PASSWORDS (CLIENT INFO, ETC)
    PRIMARY KEY(secret_id)
)

CREATE TABLE granted_secrets (
  secret_id      INTEGER,
  recipient_id   INTEGER,
  encrypted_data BINARY,
  PRIMARY KEY(secret_id, recipient_id),
  FOREIGN KEY(secret_id) REFERENCES secrets(secret_id)
  FOREIGN KEY(recipient_id) REFERENCES users(user_id)
)

在用户开始使用此系统之前,必须先注册这些系统。

function register_user(user_id, user_password) {
    authentication_salt = generate_random_salt()
    authentication_hash = hash(authentication_salt, user_password);

    (public_key, private_key) = asymmetric_cipher_generate_random_key_pair();

    decryption_key_salt = generate_random_salt()
    decryption_key = derive_key(decryption_key_salt, user_password)
    encrypted_private_key = symmetric_cipher_encrypt(
        input => private_key,
        key   => decryption_key
    )

    // IMPORTANT: The decryption_key_hash is never stored

    execute("INSERT INTO users (user_id, authentication_hash, authentication_salt, public_key, encrypted_private_key, decryption_key_salt) VALUES (:user_id, :authentication_hash, :authentication_salt, :public_key, :encrypted_private_key, :decryption_key_salt)")
}

用户可以登录系统。

function authenticate_user(user_id, user_password)
    correct_authentication_hash = query("SELECT authentication_hash FROM users WHERE user_id = :user_id")

    authentication_salt = query("SELECT authentication_salt FROM users WHERE user_id = :user_id")
    given_authentication_hash = hash(authentication_salt, user_password)

    return correct_authentication_hash == given_authentication_hash

然后可以将秘密授予收件人用户。

function grant_secret(secret_id, secret_data, recipient_id) {
    recipient_public_key = query("SELECT public_key FROM users WHERE user_id = :recipient_id")

    encrypted_secret_data = asymmetric_cipher_encrypt(
        input      => secret_data,
        public_key => recipient_public_key
    )

    execute("INSERT INTO granted_secrets (secret_id, recipient_id, encrypted_data) VALUES (:secret_id, :recipient_id, :encrypted_secret_data)")
}

最后,被授予访问权限的用户(收件人)可以检索它。

void retrieve_secret(secret_id, recipient_id, recipient_password)
    encrypted_recipient_private_key = query("SELECT encrypted_private_key FROM users WHERE user_id = :recipient_id")

    recipient_decryption_key_salt = query("SELECT decryption_key_salt FROM users WHERE user_id = :recipient_id")
    recipient_decryption_key = derive_key(recipient_decryption_key_salt, recipient_password)
    recipient_private_key = symmetric_cipher_decrypt(
        input => encrypted_recipient_private_key,
        key   => recipient_decryption_key
    )

    encrypted_secret_data = query("SELECT encrypted_data FROM granted_secrets WHERE secret_id = :secret_id AND recipient_id = :recipient_id")

    secret_data = asymmetric_cipher_decrypt(
        input       => encrypted_secret_data,
        private_key => recipient_private_key
    )

    return secret_data

希望这可以提供帮助。它确实帮助我充实了我的想法。

答案 2 :(得分:2)

使用AES和强密钥加密存储您的密码。不是将解密密钥嵌入应用程序中,而是将其提供给您的用户(支持顾问)。然后让您的应用程序在需要查找信息时提示它们。不理想,因为每个人都使用相同的密钥,但如果您的数据库受到损害,它至少会提供一些保护。

答案 3 :(得分:1)

如果您担心向顾问提供原始用户密码,那么这里是值得深思的。

如何为您的支持成员专门生成临时密码/令牌?

当客户拨打电话时,请创建一个令牌并将其与用户ID一起放入地图中。 将此令牌或加密令牌发送给您的客户支持成员。 然后,客户支持成员将使用用户名和令牌登录。 系统检查validtoken映射中的令牌并找到其有效令牌并授予对系统的访问权限。

支持memeber解决问题。 然后从tokenMap中删除令牌。

通过这种方式,您永远不会向顾问透露原始用户密码。 因此,您可以使用自己喜欢的安全技术存储用户密码。

您可以在发送令牌之前使用顾问的公钥加密令牌。

代币可能会有到期时间。

答案 4 :(得分:0)

如果您需要从数据库中检索明文密码,那么您将需要使用双向加密方案。

请勿将密钥放入客户端应用程序中。

这样,如果您担心密钥被泄露,您可以定期重新加密数据/更改密钥。虽然如果你的密钥足够强大并且不公开,你应该是安全的

通过网络连接发送密码时,请务必使用SSL加密链接。

设置可能如下所示

客户--->服务器---> DB

服务器解密密码并通过SSL链接将其传递给客户端。

如果您没有服务器且客户端直接连接到数据库,那么您可以让客户端调用存储过程来检索纯文本密码。

答案 5 :(得分:0)

这听起来像是为DPAPI设计的方案。创建一个可以验证客户端的中间层,然后在将数据存储到数据库之前使用DPAPI加密数据

答案 6 :(得分:-3)

您不需要将密码保存在数据库中,只需保留它们的哈希值(例如SHA1)。然后在登录时再次计算哈希并将其与已排序的哈希进行比较。

请相信我,如果你没有存储密码,你的肩膀上会有很多重量。