開箱即用的安全性
![]() |
圖片: TheKenChan ( CC BY-NC 2.0 ) |
GlassFish已經附帶了GlassFish JDBC領域 。 您所要做的就是初始化數據庫并正確獲得安全性配置,然后就可以完成。 在標準配置中,您可以選擇定義摘要算法(包括編碼和字符集)。 摘要算法可以是任何JDK支持的 MessageDigest(MD2,MD5,SHA-1,SHA-256,SHA-384,SHA-512)。 比較我的JDBC Security Realm帖子以獲得完整的設置。
什么是弱項或缺失項?
開箱即用的解決方案非常簡單。 它只是對密碼進行哈希處理。 有很多方法可以非常快速地從普通哈希中恢復密碼。 破解哈希的最簡單方法是嘗試猜測密碼,對每個猜測進行哈希處理,并檢查猜測的哈希是否等于被破解的哈希。 如果哈希值相等,則猜測為密碼。 猜測密碼的兩種最常見方式是字典攻擊和蠻力攻擊。 查找表也是眾所周知的。 它們是一種非常快速地破解許多相同類型哈希的有效方法。 總體思路是在密碼字典中預先計算密碼的哈希值,并將它們及其對應的密碼存儲在查找表數據結構中。 但是我們現在還沒有完成。 您還會發現稱為反向查找表的內容。 這種攻擊使攻擊者可以同時對多個散列應用字典或蠻力攻擊,而不必預先計算查找表。 最后但并非最不重要的彩虹表攻擊。 它們就像查找表,只不過它們犧牲了哈希破解速度以使查找表更小。 令人印象深刻的方法列表。 顯然,這不能滿足我個人對密碼保護的需求。
加一些鹽
上述方法之所以有效,是因為每個密碼都以完全相同的方式進行哈希處理。 每次通過安全哈希函數運行密碼時,都會產生完全相同的輸出。 防止這種情況的一種方法是在其中添加一些鹽。 在對哈希進行哈希運算之前,在密碼前添加或添加隨機字符串即可解決此問題。 該隨機字符串稱為“鹽”。 請注意,對于所有密碼重用salt并不安全。 您仍然可以使用彩虹表或字典攻擊來破解它們。 因此,您必須為每個密碼隨機分配鹽,并將其存儲在哈希密碼旁邊。 每次用戶更新密碼時,它都需要更改。 關于長度的簡短句子。 鹽不要太短。 對于最有效的長度,其長度將與密碼哈希相同。 如果使用SHA512(512/8位= 64字節),則應選擇長度至少為64個隨機字節的鹽。
準備工作
我們現在顯然已經離開了標準的JDBCRealm功能。 這意味著我們必須實現自己的安全領域。 從現在開始,我們將其稱為UserRealm。 讓我們從與JDBCRealm相同的設置開始。 具有“ jdbcrealmdb”架構的MySQL數據庫。 唯一的區別是,我們準備使用每個密碼來保存鹽。
USE jdbcrealmdb; CREATE TABLE `jdbcrealmdb`.`users` ( `username` varchar(255) NOT NULL, `salt` varchar(255) NOT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `jdbcrealmdb`.`groups` ( `username` varchar(255) DEFAULT NULL, `groupname` varchar(255) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE INDEX groups_users_FK1 ON groups(username ASC);
現在,我們實現了基本領域。 以下代碼僅顯示了強制成員。 我將在接下來的幾天中提供該資源。 直到今天,這篇文章仍然可供您使用。
public class UserRealm extends AppservRealm { /** * Init realm from properties */ protected void init(Properties props) /** * Get JAASContext */ public String getJAASContext() /** * Get AuthType */ public String getAuthType() /** * Get DB Connection */ private Connection getConnection() /** * Close Connection */ private void closeConnection(Connection cn) /** * Close prepared statement */ private void closeStatement(PreparedStatement st) /** * Make the compiler happy. */ public Enumeration getGroupNames(String string) /** * Authenticate the user */ public String[] authenticate(String userId, String password) }
但是最重??要的部分在這里丟失了。
設置一些測試
我不是那種受測試驅動的人,但在這種情況下,這確實有意義。 因為我將在此處實現的領域不支持通過GlassFish管理控制臺進行用戶管理。 因此,基本要求是要有一個準備好的數據庫,其中包含所有用戶,密碼和鹽。 我們走吧。 添加sql-maven-plugin,并使其在測試編譯階段創建表。
<plugin><groupId>org.codehaus.mojo</groupId><artifactId>sql-maven-plugin</artifactId><version>1.3</version><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.18</version></dependency></dependencies><configuration><driver>${driver}</driver><url>${url}</url><username>${username}</username><password>${password}</password><skip>${maven.test.skip}</skip><srcFiles><srcFile>src/test/data/drop-and-create-table.sql</srcFile></srcFiles></configuration><executions><execution><id>create-table</id><phase>test-compile</phase><goals><goal>execute</goal></goals></execution></executions></plugin>
您可以使用一些db-unit magic將測試數據插入數據庫中,也可以在測試用例中執行此操作。 我決定走這條路。 首先,讓我們將所有相關的JDBC內容放到一個稱為SecurityStore的單獨位置。 我們基本上需要三種方法。 添加一個用戶,為該用戶添加鹽并驗證該用戶。
private final static String ADD_USER = "INSERT INTO users VALUES(?,?,?);";private final static String SALT_FOR_USER = "SELECT salt FROM users u WHERE username = ?;";private final static String VERIFY_USER = "SELECT username FROM users u WHERE username = ? AND password = ?;"; //... public void addUser(String name, String salt, String password) {try {PreparedStatement pstm = con.prepareStatement(ADD_USER);pstm.setString(1, name);pstm.setString(2, salt);pstm.setString(3, password);pstm.executeUpdate();} catch (SQLException ex) {LOGGER.log(Level.SEVERE, "Create User failed!", ex);}}public String getSaltForUser(String name) {String salt = null;try {PreparedStatement pstm = con.prepareStatement(SALT_FOR_USER);pstm.setString(1, name);ResultSet rs = pstm.executeQuery();if (rs.next()) {salt = rs.getString(1);}} catch (SQLException ex) {LOGGER.log(Level.SEVERE, "User not found!", ex);}return salt;}public boolean validateUser(String name, String password) {try {PreparedStatement pstm = con.prepareStatement(VERIFY_USER);pstm.setString(1, name);pstm.setString(2, password);ResultSet rs = pstm.executeQuery();if (rs.next()) {return true;}} catch (SQLException ex) {LOGGER.log(Level.SEVERE, "User validation failed!", ex);}return false;}
為了在這里不要實現太多,我決定有兩個單獨的構造函數:
public SecurityStore(String dataSource) public SecurityStore(String user, String passwd)
因此,這將與應用程序服務器和本地測試一起使用。 接下來是實際的密碼和鹽邏輯。
使用密碼,哈希和鹽
這是我想出的:
public class Password {private SecureRandom random;private static final String CHARSET = "UTF-8";private static final String ENCRYPTION_ALGORITHM = "SHA-512";private BASE64Decoder decoder = new BASE64Decoder();private BASE64Encoder encoder = new BASE64Encoder();public byte[] getSalt(int length) {random = new SecureRandom();byte bytes[] = new byte[length];random.nextBytes(bytes);return bytes;}public byte[] hashWithSalt(String password, byte[] salt) {byte[] hash = null;try {byte[] bytesOfMessage = password.getBytes(CHARSET);MessageDigest md;md = MessageDigest.getInstance(ENCRYPTION_ALGORITHM);md.reset();md.update(salt);md.update(bytesOfMessage);hash = md.digest();} catch (UnsupportedEncodingException | NoSuchAlgorithmException ex) {Logger.getLogger(Password.class.getName()).log(Level.SEVERE, "Encoding Problem", ex);}return hash;}public String base64FromBytes(byte[] text) {return encoder.encode(text);}public byte[] bytesFrombase64(String text) {byte[] textBytes = null;try {textBytes = decoder.decodeBuffer(text);} catch (IOException ex) {Logger.getLogger(Password.class.getName()).log(Level.SEVERE, "Encoding failed!", ex);}return textBytes;} }
很簡單,對不對? 老實說:使用byte []可以更好地隱藏,但是我認為您會更容易理解這里發生的事情。 salt()方法返回配置長度的安全隨機鹽。 hashWithSalt()方法將所有內容放入一個SHA-512哈希密碼中。
關于結束碼
我決定對它進行Base64編碼,并且使用的是專有API(sun.misc.BASE64Decoder,Encoder)。 您應該在這里考慮使用Apache Commons。 但這是最簡單的方法。 另一種方法是簡單地對所有內容進行十六進制編碼(零填充)。 Base64和HEX之間的區別實際上只是字節的表示方式。 十六進制是表示“ Base16”的另一種方式。 十六進制將為每個字節占用兩個字符– Base64每三個字節將占用4個字符,因此它比十六進制更有效。 假設您使用UTF-8編碼XML文檔,則100K文件將需要200K進行十六進制編碼,而在Base64中則需要133K。
最后是UserRealm中缺少的方法
這篇冗長的文章的最后一部分是UserRealm類中的authenticate方法。
/*** Authenticates a user against GlassFish** @param name The user name* @param givenPwd The password to check* @return String[] of the groups a user belongs to.* @throws Exception*/public String[] authenticate(String name, String givenPwd) throws Exception {SecurityStore store = new SecurityStore(dataSource);// attempting to read the users-saltString salt = store.getSaltForUser(name);// Defaulting to a failed login by setting nullString[] result = null;if (salt != null) {Password pwd = new Password();// get the byte[] from the saltbyte[] saltBytes = pwd.bytesFrombase64(salt);// hash password and saltbyte[] passwordBytes = pwd.hashWithSalt(givenPwd, saltBytes);// Base64 encode to StringString password = pwd.base64FromBytes(passwordBytes);_logger.log(Level.FINE, "PWD Generated {0}", password);// validate password with the dbif (store.validateUser(name, password)) {result[0] = "ValidUser";}}return result;}
這就是所有要做的事情。 如果給定用戶名帶有鹽,我們將生成一個哈希密碼,該密碼將與數據庫中的密碼進行核對。 getSaltForUser()也是我們對用戶是否存在的隱式檢查。
使密碼破解更加困難:哈希函數慢
如果安全性不增加更多,則不會被稱為安全性。 因此,加鹽的密碼比簡單的散列密碼要好得多,但可能仍然不夠,因為它們仍然允許對任何單個散列進行暴力破解或字典攻擊。 但是您可以添加更多保護。 關鍵字是key-stretching 。 也稱為慢散列函數。 這里的想法是使計算速度足夠慢,從而不再允許CPU / GPU驅動的攻擊。 它使用特殊的CPU密集哈希函數實現。 PBKDF2 (基于密碼的密鑰派生功能2)就是其中之一。 您可以用不同的方式使用它,但只能警告一個:切勿自己嘗試這樣做。 使用像的測試并提供實現方式的一個PBKDF2WithHmacSHA1從JDK或PKCS5S2ParametersGenerator從BouncyCastle的庫。 一個示例可能如下所示:
public byte[] hashWithSlowsalt(String password, byte[] salt) {SecretKeyFactory factory;Key key = null;try {factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");KeySpec keyspec = new PBEKeySpec(password.toCharArray(), salt, 1000, 512);key = factory.generateSecret(keyspec);} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {Logger.getLogger(Password.class.getName()).log(Level.SEVERE, null, ex);}return key.getEncoded();}
為什么那樣呢?
我們聽說密碼和用戶數據庫泄漏很多。 每天。 一些大型站點遭到了攻擊,而實現者為其用戶提供適當的安全性基本上取決于實施者。 坦白地說,使用提供的功能很難知道在哪里進行調整以及如何進行調整,從而使您感到不舒服。 不要停止學習安全功能,并時刻注意可能出現的問題。 我個人希望GlassFish為用戶提供一套更全面的默認領域。 但只要不是這種情況,我的博客就是引導您朝正確方向發展的唯一途徑。 希望你喜歡!
參考: JCG合作伙伴 Markus Eisele在Java企業軟件開發博客上的MySQL上帶有咸密碼的GlassFish JDBC安全性 。
翻譯自: https://www.javacodegeeks.com/2012/07/glassfish-jdbc-security-with-salted.html