📄 jcecipherfactory.java
字号:
// generatedKey = generateUniqueBytes(); properties.put(RawStoreFactory.ENCRYPTED_KEY, saveSecretKey(generatedKey, bootPassword)); } else { generatedKey = getDatabaseSecretKey(properties, bootPassword, SQLState.SERVICE_WRONG_BOOT_PASSWORD); } return generatedKey; } public void stop() { } /** get the secretkey used for encryption and decryption when boot password mechanism is used for encryption Steps include retrieve the stored key, decrypt the stored key and verify if the correct boot password was passed There is a possibility that the decrypted key includes the original key and padded bytes in order to have been block size aligned during encryption phase. Hence extract the original key @param properties properties to retrieve the encrypted key @param bootPassword boot password used to connect to the encrypted database @param errorState errorstate to account for any errors during retrieval /creation of the secretKey @return the original unencrypted key bytes to use for encryption and decrytion */ private byte[] getDatabaseSecretKey(Properties properties, byte[] bootPassword, String errorState) throws StandardException { // recover the generated secret encryption key from the // services.properties file and the user key. String keyString = properties.getProperty(RawStoreFactory.ENCRYPTED_KEY); if (keyString == null) throw StandardException.newException(errorState); int encodedKeyCharLength = keyString.indexOf('-'); if (encodedKeyCharLength == -1) // bad form throw StandardException.newException(errorState); int verifyKey = Integer.parseInt(keyString.substring(encodedKeyCharLength+1)); byte[] generatedKey = decryptKey(keyString, encodedKeyCharLength, bootPassword); int checkKey = digest(generatedKey); if (checkKey != verifyKey) throw StandardException.newException(errorState); // if encodedKeyLength is not defined, then either it is an old version with no support for different // key sizes and padding except for defaults byte[] result; if(encodedKeyLength != 0) { result = new byte[encodedKeyLength]; // extract the generated key without the padding bytes System.arraycopy(generatedKey,0,result,0,encodedKeyLength); return result; } return generatedKey; } private String saveSecretKey(byte[] secretKey, byte[] bootPassword) throws StandardException { String encryptedKey = encryptKey(secretKey, bootPassword); // make a verification key out of the message digest of // the generated key int verifyKey = digest(secretKey); return encryptedKey.concat("-" + verifyKey); } public String changeBootPassword(String changeString, Properties properties, CipherProvider verify) throws StandardException { // the new bootPassword is expected to be of the form // oldkey , newkey. int seperator = changeString.indexOf(','); if (seperator == -1) throw StandardException.newException(SQLState.WRONG_PASSWORD_CHANGE_FORMAT); String oldBP = changeString.substring(0, seperator).trim(); byte[] oldBPAscii = StringUtil.getAsciiBytes(oldBP); if (oldBPAscii == null || oldBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH) throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD);; String newBP = changeString.substring(seperator+1).trim(); byte[] newBPAscii = StringUtil.getAsciiBytes(newBP); if (newBPAscii == null || newBPAscii.length < CipherFactory.MIN_BOOTPASS_LENGTH) throw StandardException.newException(SQLState.ILLEGAL_BP_LENGTH, new Integer(CipherFactory.MIN_BOOTPASS_LENGTH)); // verify old key byte[] generatedKey = getDatabaseSecretKey(properties, oldBPAscii, SQLState.WRONG_BOOT_PASSWORD); // make sure the oldKey is correct byte[] IV = generateIV(generatedKey); if (!((JCECipherProvider) verify).verifyIV(IV)) throw StandardException.newException(SQLState.WRONG_BOOT_PASSWORD); // Make the new key. The generated key is unchanged, only the // encrypted key is changed. String newkey = saveSecretKey(generatedKey, newBPAscii); properties.put(Attribute.CRYPTO_KEY_LENGTH,keyLengthBits+"-"+encodedKeyLength); return saveSecretKey(generatedKey, newBPAscii); } /** perform actions with privileges enabled. */ public final Object run() throws StandardException, InstantiationException, IllegalAccessException { try { switch(action) { case 1: Security.addProvider( (Provider)(Class.forName(cryptoProvider).newInstance())); break; case 2: // SECURITY PERMISSION - MP1 and/or OP4 // depends on the value of activePerms return activeFile.getRandomAccessFile(activePerms); } } catch (ClassNotFoundException cnfe) { throw StandardException.newException(SQLState.ENCRYPTION_NO_PROVIDER_CLASS,cryptoProvider); } catch(FileNotFoundException fnfe) { throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,cryptoProvider); } return null; } /** The database can be encrypted with an encryption key given in connection url. For security reasons, this key is not made persistent in the database. But it is necessary to verify the encryption key when booting the database if it is similar to the one used when creating the database This needs to happen before we access the data/logs to avoid the risk of corrupting the database because of a wrong encryption key. This method performs the steps necessary to verify the encryption key if an external encryption key is given. At database creation, 4k of random data is generated using SecureRandom and MD5 is used to compute the checksum for the random data thus generated. This 4k page of random data is then encrypted using the encryption key. The checksum of unencrypted data and encrypted data is made persistent in the database in file by name given by Attribute.CRYPTO_EXTERNAL_KEY_VERIFYFILE (verifyKey.dat). This file exists directly under the database root directory. When trying to boot an existing encrypted database, the given encryption key is used to decrypt the data in the verifyKey.dat and the checksum is calculated and compared against the original stored checksum. If these checksums dont match an exception is thrown. Please note, this process of verifying the key does not provide any added security but only is intended to allow to fail gracefully if a wrong encryption key is used @return StandardException is thrown if there are any problems during the process of verification of the encryption key or if there is any mismatch of the encryption key. */ public void verifyKey(boolean create, StorageFactory sf, Properties properties) throws StandardException { if(properties.getProperty(Attribute.CRYPTO_EXTERNAL_KEY) == null) return; // if firstTime ( ie during creation of database, initial key used ) // In order to allow for verifying the external key for future database boot, // generate random 4k of data and store the encrypted random data and the checksum // using MD5 of the unencrypted data. That way, on next database boot a check is performed // to verify if the key is the same as used when the database was created StorageRandomAccessFile verifyKeyFile = null; byte[] data = new byte[VERIFYKEY_DATALEN]; try { if(create) { getSecureRandom().nextBytes(data); // get the checksum byte[] checksum = getMD5Checksum(data); CipherProvider tmpCipherProvider = createNewCipher(ENCRYPT,mainSecretKey,mainIV); tmpCipherProvider.encrypt(data, 0, data.length, data, 0); // openFileForWrite verifyKeyFile = privAccessFile(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE,"rw"); // write the checksum length as int, and then the checksum and then the encrypted data verifyKeyFile.writeInt(checksum.length); verifyKeyFile.write(checksum); verifyKeyFile.write(data); verifyKeyFile.sync(true); } else { // open file for reading only verifyKeyFile = privAccessFile(sf,Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE,"r"); // then read the checksum length int checksumLen = verifyKeyFile.readInt(); byte[] originalChecksum = new byte[checksumLen]; verifyKeyFile.readFully(originalChecksum); verifyKeyFile.readFully(data); // decrypt data with key CipherProvider tmpCipherProvider = createNewCipher(DECRYPT,mainSecretKey,mainIV); tmpCipherProvider.decrypt(data, 0, data.length, data, 0); byte[] verifyChecksum = getMD5Checksum(data); if(!MessageDigest.isEqual(originalChecksum,verifyChecksum)) { throw StandardException.newException(SQLState.ENCRYPTION_BAD_EXTERNAL_KEY); } } } catch(IOException ioe) { throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioe); } finally { try { if(verifyKeyFile != null) verifyKeyFile.close(); } catch(IOException ioee) { throw StandardException.newException(SQLState.ENCRYPTION_UNABLE_KEY_VERIFICATION,ioee); } } return ; } /** Use MD5 MessageDigest algorithm to generate checksum @param data data to be used to compute the hash value @return returns the hash value computed using the data */ private byte[] getMD5Checksum(byte[] data) throws StandardException { try { // get the checksum MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(data); } catch(NoSuchAlgorithmException nsae) { throw StandardException.newException(SQLState.ENCRYPTION_BAD_ALG_FORMAT,MESSAGE_DIGEST); } } /** access a file for either read/write @param storageFactory factory used for io access @param fileName name of the file to create and open for write The file will be created directly under the database root directory @param filePerms file permissions, if "rw" open file with read and write permissions if "r" , open file with read permissions @return StorageRandomAccessFile returns file with fileName for writing @exception IOException Any exception during accessing the file for read/write */ private StorageRandomAccessFile privAccessFile(StorageFactory storageFactory,String fileName,String filePerms) throws java.io.IOException { StorageFile verifyKeyFile = storageFactory.newStorageFile("",fileName); activeFile = verifyKeyFile; this.action = 2; activePerms = filePerms; try { return (StorageRandomAccessFile)java.security.AccessController.doPrivileged(this); } catch( java.security.PrivilegedActionException pae) { throw (java.io.IOException)pae.getException(); } }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -