提交 94504a09 authored 作者: Thomas Mueller's avatar Thomas Mueller

Password hash: in addition to connecting with the plain text password, H2 now…

Password hash: in addition to connecting with the plain text password, H2 now supports connecting with the password hash.
上级 cde57a92
...@@ -52,6 +52,10 @@ Advanced ...@@ -52,6 +52,10 @@ Advanced
File Locking Protocols</a><br /> File Locking Protocols</a><br />
<a href="#file_locking_serialized"> <a href="#file_locking_serialized">
File Locking Method 'Serialized'</a><br /> File Locking Method 'Serialized'</a><br />
<a href="#passwords">
Using Passwords</a><br />
<a href="#password_hash">
Password Hash</a><br />
<a href="#sql_injection"> <a href="#sql_injection">
Protection against SQL Injection</a><br /> Protection against SQL Injection</a><br />
<a href="#remote_access"> <a href="#remote_access">
...@@ -897,6 +901,106 @@ This feature is relatively new. When using it for production, please ensure ...@@ -897,6 +901,106 @@ This feature is relatively new. When using it for production, please ensure
your use case is well tested (if possible with automated test cases). your use case is well tested (if possible with automated test cases).
</p> </p>
<h2 id="passwords">Using Passwords</h2>
<h3>Using Secure Passwords</h3>
<p>
Remember that weak passwords can be broken regardless of the encryption and security protocols.
Don't use passwords that can be found in a dictionary. Appending numbers does not make passwords
secure. A way to create good passwords that can be remembered is: take the first
letters of a sentence, use upper and lower case characters, and creatively include special characters
(but it's more important to use a long password than to use special characters).
Example:
</p><p>
<code>i'sE2rtPiUKtT</code> from the sentence <code>it's easy to remember this password if you know the trick</code>.
</p>
<h3>Passwords: Using Char Arrays instead of Strings</h3>
<p>
Java strings are immutable objects and cannot be safely 'destroyed' by the application.
After creating a string, it will remain in the main memory of the computer at least
until it is garbage collected. The garbage collection cannot be controlled by the application,
and even if it is garbage collected the data may still remain in memory.
It might also be possible that the part of memory containing the password
is swapped to disk (if not enough main memory is available), which is
a problem if the attacker has access to the swap file of the operating system.
</p><p>
It is a good idea to use char arrays instead of strings for passwords.
Char arrays can be cleared (filled with zeros) after use, and therefore the
password will not be stored in the swap file.
</p><p>
This database supports using char arrays instead of string to pass user and file passwords.
The following code can be used to do that:
</p>
<pre>
import java.sql.*;
import java.util.*;
public class Test {
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:~/test";
Properties prop = new Properties();
prop.setProperty("user", "sa");
System.out.print("Password?");
char[] password = System.console().readPassword();
prop.put("password", password);
Connection conn = null;
try {
conn = DriverManager.getConnection(url, prop);
} finally {
Arrays.fill(password, (char) 0);
}
conn.close();
}
}
</pre>
<p>
This example requires Java 1.6.
When using Swing, use <code>javax.swing.JPasswordField</code>.
</p>
<h3>Passing the User Name and/or Password in the URL</h3>
<p>
Instead of passing the user name as a separate parameter as in
<code>
Connection conn = DriverManager.
getConnection("jdbc:h2:~/test", "sa", "123");
</code>
the user name (and/or password) can be supplied in the URL itself:
<code>
Connection conn = DriverManager.
getConnection("jdbc:h2:~/test;USER=sa;PASSWORD=123");
</code>
The settings in the URL override the settings passed as a separate parameter.
</p>
<h2 id="password_hash">Password Hash</h2>
<p>
Sometimes the database password needs to be stored in a configuration file
(for example in the <code>web.xml</code> file).
In addition to connecting with the plain text password,
this database supports connecting with the password hash.
This means that only the hash of the password (and not the plain text password)
needs to be stored in the configuration file.
This will only protect others from reading or re-constructing the plain text password
(even if they have access to the configuration file);
it does not protect others from accessing the database using the password hash.
</p>
<p>
To connect using the password hash instead of plain text password, append
<code>;PASSWORD_HASH=TRUE</code> to the database URL, and replace
the password with the password hash. To calculate the password hash from a plain text password,
run the following command within the H2 Console tool:
<code>@password_hash &lt;upperCaseUserName&gt; &lt;password&gt;</code>.
As an example, if the user name is <code>sa</code> and the password is
<code>test</code>, run the command
<code>@password_hash SA test</code>.
Then use the resulting password hash as you would use the plain text password.
When using an encrypted database, then the user password and file password
need to be hashed separately. To calculate the hash of the file password, run:
<code>@password_hash file &lt;filePassword&gt;</code>.
</p>
<h2 id="sql_injection">Protection against SQL Injection</h2> <h2 id="sql_injection">Protection against SQL Injection</h2>
<h3>What is SQL Injection</h3> <h3>What is SQL Injection</h3>
<p> <p>
......
...@@ -79,7 +79,7 @@ public class ConnectionInfo implements Cloneable { ...@@ -79,7 +79,7 @@ public class ConnectionInfo implements Cloneable {
String[] connectionTime = { "ACCESS_MODE_DATA", "AUTOCOMMIT", "CIPHER", String[] connectionTime = { "ACCESS_MODE_DATA", "AUTOCOMMIT", "CIPHER",
"CREATE", "CACHE_TYPE", "DB_CLOSE_ON_EXIT", "FILE_LOCK", "IGNORE_UNKNOWN_SETTINGS", "IFEXISTS", "CREATE", "CACHE_TYPE", "DB_CLOSE_ON_EXIT", "FILE_LOCK", "IGNORE_UNKNOWN_SETTINGS", "IFEXISTS",
"INIT", "PASSWORD", "RECOVER", "USER", "AUTO_SERVER", "INIT", "PASSWORD", "RECOVER", "USER", "AUTO_SERVER",
"AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE" }; "AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE", "PASSWORD_HASH" };
for (String key : connectionTime) { for (String key : connectionTime) {
if (SysProperties.CHECK && set.contains(key)) { if (SysProperties.CHECK && set.contains(key)) {
DbException.throwInternalError(key); DbException.throwInternalError(key);
...@@ -245,7 +245,7 @@ public class ConnectionInfo implements Cloneable { ...@@ -245,7 +245,7 @@ public class ConnectionInfo implements Cloneable {
*/ */
private void convertPasswords() { private void convertPasswords() {
char[] password = removePassword(); char[] password = removePassword();
SHA256 sha = new SHA256(); boolean passwordHash = removeProperty("PASSWORD_HASH", false);
if (getProperty("CIPHER", null) != null) { if (getProperty("CIPHER", null) != null) {
// split password into (filePassword+' '+userPassword) // split password into (filePassword+' '+userPassword)
int space = -1; int space = -1;
...@@ -264,9 +264,18 @@ public class ConnectionInfo implements Cloneable { ...@@ -264,9 +264,18 @@ public class ConnectionInfo implements Cloneable {
System.arraycopy(password, 0, filePassword, 0, space); System.arraycopy(password, 0, filePassword, 0, space);
Arrays.fill(password, (char) 0); Arrays.fill(password, (char) 0);
password = np; password = np;
filePasswordHash = sha.getKeyPasswordHash("file", filePassword); filePasswordHash = hashPassword(passwordHash, "file", filePassword);
}
userPasswordHash = hashPassword(passwordHash, user, password);
}
private byte[] hashPassword(boolean passwordHash, String userName, char[] password) {
if (passwordHash) {
return StringUtils.convertStringToBytes(new String(password));
} else {
SHA256 sha = new SHA256();
return sha.getKeyPasswordHash(userName, password);
} }
userPasswordHash = sha.getKeyPasswordHash(user, password);
} }
/** /**
......
...@@ -39,6 +39,7 @@ import org.h2.constant.SysProperties; ...@@ -39,6 +39,7 @@ import org.h2.constant.SysProperties;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.jdbc.JdbcSQLException; import org.h2.jdbc.JdbcSQLException;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.security.SHA256;
import org.h2.tools.Backup; import org.h2.tools.Backup;
import org.h2.tools.ChangeFileEncryption; import org.h2.tools.ChangeFileEncryption;
import org.h2.tools.ConvertTraceFile; import org.h2.tools.ConvertTraceFile;
...@@ -1270,6 +1271,11 @@ public class WebApp { ...@@ -1270,6 +1271,11 @@ public class WebApp {
PreparedStatement prep = conn.prepareStatement(sql); PreparedStatement prep = conn.prepareStatement(sql);
buff.append(getParameterResultSet(prep.getParameterMetaData())); buff.append(getParameterResultSet(prep.getParameterMetaData()));
return buff.toString(); return buff.toString();
} else if (isBuiltIn(sql, "@password_hash")) {
sql = sql.substring("@password_hash".length()).trim();
String[] p = split(sql);
SHA256 sha = new SHA256();
return StringUtils.convertBytesToString(sha.getKeyPasswordHash(p[0], p[1].toCharArray()));
} else if (isBuiltIn(sql, "@prof_start")) { } else if (isBuiltIn(sql, "@prof_start")) {
if (profiler != null) { if (profiler != null) {
profiler.stopCollecting(); profiler.stopCollecting();
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
*/ */
package org.h2.test.unit; package org.h2.test.unit;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.h2.security.BlockCipher; import org.h2.security.BlockCipher;
import org.h2.security.CipherFactory; import org.h2.security.CipherFactory;
import org.h2.security.SHA256; import org.h2.security.SHA256;
...@@ -27,12 +30,21 @@ public class TestSecurity extends TestBase { ...@@ -27,12 +30,21 @@ public class TestSecurity extends TestBase {
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
public void test() { public void test() throws SQLException {
testConnectWithHash();
testSHA(); testSHA();
testAES(); testAES();
testXTEA(); testXTEA();
} }
private void testConnectWithHash() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:h2:mem:test", "sa", "sa");
String pwd = StringUtils.convertBytesToString(new SHA256().getKeyPasswordHash("SA", "sa".toCharArray()));
Connection conn2 = DriverManager.getConnection("jdbc:h2:mem:test;PASSWORD_HASH=TRUE", "sa", pwd);
conn.close();
conn2.close();
}
private void testSHA() { private void testSHA() {
SHA256 sha = new SHA256(); SHA256 sha = new SHA256();
testOneSHA(sha); testOneSHA(sha);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论