根据盐值加密/解密一个字符串

6
我想知道在我的数据库中是否可能存储一个加密的文本字段,并根据盐值和授权密码对这段文本进行解密?
例如:
$Salt = $_POST['Salt']; 
$Password = $Query_Results['Password'];

if ($salt == $Stored_Salt AND $Authorized_Password == $Password){
  //Perform a decryption of the stored results
   echo $Decrypted_TextField;
}

我正在创建一个完全加密/编码的数据库。除标识符的整数字段外,没有任何明文内容。其他所有内容都将被加密/编码。许多字段将使用单向加密,但是某些字段需要使用双向加密方式。

我不能在数据库中存储非加密文本。所有内容都需要在存储之前进行加密。但是,我对此方法尚不熟悉。因此,我想知道是否可以获得一些关于如何开始的帮助。


查找对称加密算法AES。 - Patashu
@Patashu,php.net上AES函数解密的文档:http://www.php.net/manual/en/function.mcrypt-decrypt.php未记录如何实际实现这些函数。如果我提供一个成功的加密函数,您能否为此提供一些帮助? - Sophie Mackeral
你应该阅读有关对称加密和AES工作原理的文献。然后,该函数的所有参数将变得明显。 - Patashu
这个页面上的第一个评论可能会有所帮助:http://php.net/manual/en/book.mcrypt.php,还有这个主题https://dev59.com/nGgv5IYBdhLWcg3wBMEK。 - edwardmp
PGP 也有对称加密模式。 - Orangepill
显示剩余2条评论
2个回答

10
听起来你可能需要对加密货币有更多的背景了解,特别是DB加密选项(虽然你没有提到数据存储方式,但是MySql和Postgres提供了完整加密选项)。通常情况下,“自己动手”的做法几乎总是不明智的。遗憾的是,对于一个新手来说,在mcrypt()和openssl_*()函数之间提供的选择实在太多了(例如将EBC和CBC等同视为有效选项),这会让人犯迷糊。虽然这个问题:https://security.stackexchange.com/questions/18197/why-shouldnt-we-roll-our-own主要讨论创建“新颖”的加密原语的徒劳无益,但这个原则也适用于应用程序和数据库级别的加密。

作为一个实际问题,你可能需要面对的最具挑战性的事情是密码/密钥管理问题。下面的代码把所有责任都放在客户端(发送方)身上 - 除非你保存提交过来的密码(这有点违背了整个目的),否则如果用户忘记或无法在未来提供他们的密码,数据库中的加密数据将是不能恢复的。(是的,如果你真的想走下Yak Shaving的道路,还有多重密钥信封加密选项)。

如果你将密钥/密码存储在服务器端,最好只是在对手路径上设置了一个小的障碍:如果她能够读取你的密钥文件,她就可以检索数据。但最糟糕的是,通过本地保存密码,你为用户提供了虚假的安全感,如果这是财务、健康或其他受保护的信息,你和你的组织将承担责任的负担。

最后,这里有一个成熟的库:http://phpseclib.sourceforge.net/crypt/examples.html但我认为它为新手用户提供了太多的选项(例如代码生成器中的默认EBC模式)。对于密码哈希,在这里仔细看看phpPass库:http://www.openwall.com/phpass/

总之,这里是一个简单的、双向的、相当强大的加密方案,它具有随机生成的初始化向量和盐,并且使用256位AES对称(即非公钥)密码。在OSX Lion和CentOS/RedHat 6上进行过测试。

祝你好运!

//$message = escapeshellarg( $_POST['message'] );
$message = 'This is my very secret data SSN# 009-68-1234';  

// Set to some reasonable limit for DB.
// Make sure to size DB column +60 chars 
$max_msg_size = 1000;
$message = substr($message, 0, $max_msg_size);

// User's password (swap for actual form post)
//$password = escapeshellarg( $_POST['password'] );
$password = 'opensesame';

// Salt to add entropy to users' supplied passwords
// Make sure to add complexity/length requirements to users passwords!
// Note: This does not need to be kept secret
$salt = sha1(mt_rand());

// Initialization Vector, randomly generated and saved each time
// Note: This does not need to be kept secret
$iv = substr(sha1(mt_rand()), 0, 16);

echo "\n Password: $password \n Message: $message \n Salt: $salt \n IV: $iv\n";

$encrypted = openssl_encrypt(
  "$message", 'aes-256-cbc', "$salt:$password", null, $iv
);

$msg_bundle = "$salt:$iv:$encrypted";
echo " Encrypted bundle = $msg_bundle \n\n ";

// Save it... (make sure to use bind variables/prepared statements!)
/* db_write( "insert into sensitive_table encrypted_msg values (:msg_bundle)",
    $msg_bundle ); */

现在进行检索操作:
//  Retrieve from DB... 

//$password = escapeshellarg( $_POST['password'] );
$password = 'opensesame';

// Swap with actual db retrieval code here
//$saved_bundle = db_read( "select encrypted_msg from sensitive_table" );
$saved_bundle = $msg_bundle;

// Parse iv and encrypted string segments
$components = explode( ':', $saved_bundle );;

var_dump($components);

$salt          = $components[0];
$iv            = $components[1];
$encrypted_msg = $components[2];

$decrypted_msg = openssl_decrypt(
  "$encrypted_msg", 'aes-256-cbc', "$salt:$password", null, $iv
);

if ( $decrypted_msg === false ) {
  die("Unable to decrypt message! (check password) \n");
}

$msg = substr( $decrypted_msg, 41 );
echo "\n Decrypted message: $decrypted_msg \n";

样例输出:

 Password: opensesame 
 Message: This is my very secret data SSN# 009-68-1234 

 Salt: 3f12ce187d5c5bcc3b0d5acf1e76fad8b684ff37 
 IV: 00c1d3b4c6a6f4c3 

 Encrypted bundle = 3f12ce187d5c5bcc3b0d5acf1e76fad8b684ff37:00c1d3b4c6a6f4c3:KB6k+GlM+0EHbETUgEe8Lck0nF5qBz+51wc5LtmS4XMOm0Pfyyr2PIXMVEyzs/41 

 array(3) {
  [0]=>
  string(40) "3f12ce187d5c5bcc3b0d5acf1e76fad8b684ff37"
  [1]=>
  string(16) "00c1d3b4c6a6f4c3"
  [2]=>
  string(64) "KB6k+GlM+0EHbETUgEe8Lck0nF5qBz+51wc5LtmS4XMOm0Pfyyr2PIXMVEyzs/41"
}

 Decrypted message: This is my very secret data SSN# 009-68-1234 

小心,OpenSSL方法已更改,第四个参数现在被定义为整数类型,不再接受null。Sha1现在也需要一个整数作为参数,而mt_rand返回字符串。 - Greco Jonathan

2
不是一个完整的答案,而是关于盐值如何工作的扩展,它太长了,不能放在评论中。
盐不应该被视为要比较的字符串,它应该是密码的一部分,用户无需输入但对于该用户来说是唯一的。它用于防止单个受损密码破坏多个帐户。
例如,让我们假设我们有一个非常简单的系统,Bob的密码为“ABCDEF”。
通过哈希算法传递“ABCDEF”会得到(假设)“ED6522687”
如果攻击者获得了密码列表的访问权限,他们只能看到存储的哈希值。
当然,如果Jane也使用相同的密码,她的哈希值也将是“ED6522687”-这意味着如果您通过暴力破解、社交工程等方式进入任何一个帐户,您将可以访问两个帐户,因为您可以看到他们的哈希值匹配。
盐是在哈希之前对密码进行的某种操作,这是每个用户和可重复的唯一的。盐应该是可预测的,所以让我们假设Bob和Jane的盐是随机数。
现在,如果你为Bob的密码“ABCDEF123”哈希,你会得到一个不同于Jane的“ABCDEF456”的哈希。
请注意,这不是完整的解释。还有一些其他要考虑的事情:
- 在这种情况下,不存在所谓的随机数,只有密码学安全的随机数-它们的随机性有关于熵和其他有趣的东西。 - 哈希计算速度的快慢是影响防止暴力破解的主要因素-像bcrypt这样的哈希算法被设计为计算成本高昂,而不像SHA2等哈希算法。 - 用户没有理由提交(甚至知道)他们的盐。
另一个观察通常没有强调得足够...您不应该相信您在互联网上读到的关于这个主题的任何内容-有太多人对此有不完整的理解(我认为自己也是其中之一)。因此,将其视为不进行操作的原因,而不是如何操作的指南。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接