在MySQL上使用带密码的GlassFish JDBC安全性
開(kāi)箱即用的安全性
| 圖片: TheKenChan ( CC BY-NC 2.0 ) |
GlassFish已經(jīng)附帶了GlassFish JDBC領(lǐng)域 。 您所要做的就是初始化數(shù)據(jù)庫(kù)并正確獲得安全性配置,然后就可以完成。 在標(biāo)準(zhǔn)配置中,您可以選擇定義摘要算法(包括編碼和字符集)。 摘要算法可以是任何JDK支持的 MessageDigest(MD2,MD5,SHA-1,SHA-256,SHA-384,SHA-512)。 比較我的JDBC Security Realm帖子以獲得完整的設(shè)置。
什么是弱項(xiàng)或缺失項(xiàng)?
開(kāi)箱即用的解決方案非常簡(jiǎn)單。 它只是對(duì)密碼進(jìn)行哈希處理。 有很多方法可以非常快速地從普通哈希中恢復(fù)密碼。 破解哈希的最簡(jiǎn)單方法是嘗試猜測(cè)密碼,對(duì)每個(gè)猜測(cè)進(jìn)行哈希處理,并檢查猜測(cè)的哈希是否等于被破解的哈希。 如果哈希值相等,則猜測(cè)為密碼。 猜測(cè)密碼的兩種最常見(jiàn)方式是字典攻擊和蠻力攻擊。 查找表也是眾所周知的。 它們是一種非常快速地破解許多相同類(lèi)型哈希的有效方法。 總體思路是在密碼字典中預(yù)先計(jì)算密碼的哈希值,并將它們及其對(duì)應(yīng)的密碼存儲(chǔ)在查找表數(shù)據(jù)結(jié)構(gòu)中。 但是我們現(xiàn)在還沒(méi)有完成。 您還會(huì)發(fā)現(xiàn)稱(chēng)為反向查找表的內(nèi)容。 這種攻擊使攻擊者可以同時(shí)對(duì)多個(gè)散列應(yīng)用字典或蠻力攻擊,而不必預(yù)先計(jì)算查找表。 最后但并非最不重要的彩虹表攻擊。 它們就像查找表,只不過(guò)它們犧牲了哈希破解速度以使查找表更小。 令人印象深刻的方法列表。 顯然,這不能滿足我個(gè)人對(duì)密碼保護(hù)的需求。
加一些鹽
上述方法之所以有效,是因?yàn)槊總€(gè)密碼都以完全相同的方式進(jìn)行哈希處理。 每次通過(guò)安全哈希函數(shù)運(yùn)行密碼時(shí),都會(huì)產(chǎn)生完全相同的輸出。 防止這種情況的一種方法是在其中添加一些鹽。 在對(duì)哈希進(jìn)行哈希運(yùn)算之前,在密碼前添加或添加隨機(jī)字符串即可解決此問(wèn)題。 該隨機(jī)字符串稱(chēng)為“鹽”。 請(qǐng)注意,對(duì)于所有密碼重用salt并不安全。 您仍然可以使用彩虹表或字典攻擊來(lái)破解它們。 因此,您必須為每個(gè)密碼隨機(jī)分配鹽,并將其存儲(chǔ)在哈希密碼旁邊。 每次用戶(hù)更新密碼時(shí),它都需要更改。 關(guān)于長(zhǎng)度的簡(jiǎn)短句子。 鹽不要太短。 對(duì)于最有效的長(zhǎng)度,其長(zhǎng)度將與密碼哈希相同。 如果使用SHA512(512/8位= 64字節(jié)),則應(yīng)選擇長(zhǎng)度至少為64個(gè)隨機(jī)字節(jié)的鹽。
準(zhǔn)備工作
我們現(xiàn)在顯然已經(jīng)離開(kāi)了標(biāo)準(zhǔn)的JDBCRealm功能。 這意味著我們必須實(shí)現(xiàn)自己的安全領(lǐng)域。 從現(xiàn)在開(kāi)始,我們將其稱(chēng)為UserRealm。 讓我們從與JDBCRealm相同的設(shè)置開(kāi)始。 具有“ jdbcrealmdb”架構(gòu)的MySQL數(shù)據(jù)庫(kù)。 唯一的區(qū)別是,我們準(zhǔn)備使用每個(gè)密碼來(lái)保存鹽。
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);現(xiàn)在,我們實(shí)現(xiàn)了基本領(lǐng)域。 以下代碼僅顯示了強(qiáng)制成員。 我將在接下來(lái)的幾天中提供該資源。 直到今天,這篇文章仍然可供您使用。
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) }但是最重??要的部分在這里丟失了。
設(shè)置一些測(cè)試
我不是那種受測(cè)試驅(qū)動(dòng)的人,但在這種情況下,這確實(shí)有意義。 因?yàn)槲覍⒃诖颂帉?shí)現(xiàn)的領(lǐng)域不支持通過(guò)GlassFish管理控制臺(tái)進(jìn)行用戶(hù)管理。 因此,基本要求是要有一個(gè)準(zhǔn)備好的數(shù)據(jù)庫(kù),其中包含所有用戶(hù),密碼和鹽。 我們走吧。 添加sql-maven-plugin,并使其在測(cè)試編譯階段創(chuàng)建表。
<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將測(cè)試數(shù)據(jù)插入數(shù)據(jù)庫(kù)中,也可以在測(cè)試用例中執(zhí)行此操作。 我決定走這條路。 首先,讓我們將所有相關(guān)的JDBC內(nèi)容放到一個(gè)稱(chēng)為SecurityStore的單獨(dú)位置。 我們基本上需要三種方法。 添加一個(gè)用戶(hù),為該用戶(hù)添加鹽并驗(yàn)證該用戶(hù)。
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;}為了在這里不要實(shí)現(xiàn)太多,我決定有兩個(gè)單獨(dú)的構(gòu)造函數(shù):
public SecurityStore(String dataSource) public SecurityStore(String user, String passwd)因此,這將與應(yīng)用程序服務(wù)器和本地測(cè)試一起使用。 接下來(lái)是實(shí)際的密碼和鹽邏輯。
使用密碼,哈希和鹽
這是我想出的:
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;} }很簡(jiǎn)單,對(duì)不對(duì)? 老實(shí)說(shuō):使用byte []可以更好地隱藏,但是我認(rèn)為您會(huì)更容易理解這里發(fā)生的事情。 salt()方法返回配置長(zhǎng)度的安全隨機(jī)鹽。 hashWithSalt()方法將所有內(nèi)容放入一個(gè)SHA-512哈希密碼中。
關(guān)于結(jié)束碼
我決定對(duì)它進(jìn)行Base64編碼,并且使用的是專(zhuān)有API(sun.misc.BASE64Decoder,Encoder)。 您應(yīng)該在這里考慮使用Apache Commons。 但這是最簡(jiǎn)單的方法。 另一種方法是簡(jiǎn)單地對(duì)所有內(nèi)容進(jìn)行十六進(jìn)制編碼(零填充)。 Base64和HEX之間的區(qū)別實(shí)際上只是字節(jié)的表示方式。 十六進(jìn)制是表示“ Base16”的另一種方式。 十六進(jìn)制將為每個(gè)字節(jié)占用兩個(gè)字符– Base64每三個(gè)字節(jié)將占用4個(gè)字符,因此它比十六進(jìn)制更有效。 假設(shè)您使用UTF-8編碼XML文檔,則100K文件將需要200K進(jìn)行十六進(jìn)制編碼,而在Base64中則需要133K。
最后是UserRealm中缺少的方法
這篇冗長(zhǎng)的文章的最后一部分是UserRealm類(lèi)中的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;}這就是所有要做的事情。 如果給定用戶(hù)名帶有鹽,我們將生成一個(gè)哈希密碼,該密碼將與數(shù)據(jù)庫(kù)中的密碼進(jìn)行核對(duì)。 getSaltForUser()也是我們對(duì)用戶(hù)是否存在的隱式檢查。
使密碼破解更加困難:哈希函數(shù)慢
如果安全性不增加更多,則不會(huì)被稱(chēng)為安全性。 因此,加鹽的密碼比簡(jiǎn)單的散列密碼要好得多,但可能仍然不夠,因?yàn)樗鼈內(nèi)匀辉试S對(duì)任何單個(gè)散列進(jìn)行暴力破解或字典攻擊。 但是您可以添加更多保護(hù)。 關(guān)鍵字是key-stretching 。 也稱(chēng)為慢散列函數(shù)。 這里的想法是使計(jì)算速度足夠慢,從而不再允許CPU / GPU驅(qū)動(dòng)的攻擊。 它使用特殊的CPU密集哈希函數(shù)實(shí)現(xiàn)。 PBKDF2 (基于密碼的密鑰派生功能2)就是其中之一。 您可以用不同的方式使用它,但只能警告一個(gè):切勿自己嘗試這樣做。 使用像的測(cè)試并提供實(shí)現(xiàn)方式的一個(gè)PBKDF2WithHmacSHA1從JDK或PKCS5S2ParametersGenerator從BouncyCastle的庫(kù)。 一個(gè)示例可能如下所示:
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();}為什么那樣呢?
我們聽(tīng)說(shuō)密碼和用戶(hù)數(shù)據(jù)庫(kù)泄漏很多。 每天。 一些大型站點(diǎn)遭到了攻擊,而實(shí)現(xiàn)者為其用戶(hù)提供適當(dāng)?shù)陌踩曰旧先Q于實(shí)施者。 坦白地說(shuō),使用提供的功能很難知道在哪里進(jìn)行調(diào)整以及如何進(jìn)行調(diào)整,從而使您感到不舒服。 不要停止學(xué)習(xí)安全功能,并時(shí)刻注意可能出現(xiàn)的問(wèn)題。 我個(gè)人希望GlassFish為用戶(hù)提供一套更全面的默認(rèn)領(lǐng)域。 但只要不是這種情況,我的博客就是引導(dǎo)您朝正確方向發(fā)展的唯一途徑。 希望你喜歡!
參考: JCG合作伙伴 Markus Eisele在Java企業(yè)軟件開(kāi)發(fā)博客上的MySQL上帶有咸密碼的GlassFish JDBC安全性 。
翻譯自: https://www.javacodegeeks.com/2012/07/glassfish-jdbc-security-with-salted.html
總結(jié)
以上是生活随笔為你收集整理的在MySQL上使用带密码的GlassFish JDBC安全性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java EE6装饰器:在注入时装饰类
- 下一篇: 盗版电脑系统有哪些软件(电脑盗版软件下载