use hashed/salted token

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2018-08-29 14:28:45 +02:00
parent 415afeb4e3
commit c73ffe0e0c
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
5 changed files with 116 additions and 55 deletions

View file

@ -258,6 +258,19 @@ public class EncryptionTestIT {
}
}
@Test
public void testSHA512() {
// sent to 3rd party app in cleartext
String token = "4ae5978bf5354cd284b539015d442141";
String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
// stored in database
String hashedToken = EncryptionUtils.generateSHA512(token, salt);
// check: use passed cleartext and salt to verify hashed token
assertTrue(EncryptionUtils.verifySHA512(hashedToken, token));
}
// Helper
private boolean compareJsonStrings(String expected, String actual) {
@ -362,4 +375,4 @@ public class EncryptionTestIT {
return temp;
}
}
}

View file

@ -33,8 +33,8 @@ import android.util.Log;
import com.nextcloud.android.sso.aidl.IInputStreamService;
import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil;
import com.owncloud.android.authentication.AccountAuthenticator;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.db.PreferenceManager;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManager;
@ -217,12 +217,11 @@ public class InputStreamBinder extends IInputStreamService.Stub {
}
private boolean isValid(NextcloudRequest request) {
if(request.getPackageName() == null) {
String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());
request.setPackageName(callingPackageName);
}
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String storedToken = sharedPreferences.getString(request.getPackageName(), "");
String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());
SharedPreferences sharedPreferences = context.getSharedPreferences(AccountAuthenticator.SSO_SHARED_PREFERENCE,
Context.MODE_PRIVATE);
String storedToken = sharedPreferences.getString(callingPackageName, "");
return request.validateToken(storedToken);
}
}

View file

@ -19,6 +19,8 @@
package com.nextcloud.android.sso.aidl;
import com.owncloud.android.utils.EncryptionUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
@ -81,11 +83,6 @@ public class NextcloudRequest implements Serializable {
return this;
}
public Builder setPackageName(String packageName) {
ncr.packageName = packageName;
return this;
}
public Builder setAccountName(String accountName) {
ncr.accountName = accountName;
return this;
@ -151,9 +148,13 @@ public class NextcloudRequest implements Serializable {
}
public boolean validateToken(String token) {
String salt = this.token.split("\\$")[1]; // TODO extract "$"
String newHash = EncryptionUtils.generateSHA512(token, salt);
// As discussed with Lukas R. at the Nextcloud Conf 2018, always compare whole strings
// and don't exit prematurely if the string does not match anymore to prevent timing-attacks
return isEqual(this.token.getBytes(), token.getBytes());
return isEqual(this.token.getBytes(), newHash.getBytes());
}
// Taken from http://codahale.com/a-lesson-in-timing-attacks/

View file

@ -36,11 +36,11 @@ import android.widget.Toast;
import com.nextcloud.android.sso.Constants;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.db.PreferenceManager;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.utils.EncryptionUtils;
import java.util.UUID;
@ -64,6 +64,8 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
public static final String KEY_REQUIRED_FEATURES = "requiredFeatures";
public static final String KEY_LOGIN_OPTIONS = "loginOptions";
public static final String KEY_ACCOUNT = "account";
public static final String SSO_SHARED_PREFERENCE = "sso";
private static final String NEXTCLOUD_SSO = "NextcloudSSO";
private static final String TAG = AccountAuthenticator.class.getSimpleName();
@ -171,17 +173,16 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
return result;
}
// get or create token
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String token = sharedPreferences.getString(packageName, "");
// create token
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SSO_SHARED_PREFERENCE,
Context.MODE_PRIVATE);
String token = UUID.randomUUID().toString().replaceAll("-", "");
if (token.isEmpty()) {
token = UUID.randomUUID().toString().replaceAll("-", "");
String hashedTokenWithSalt = EncryptionUtils.generateSHA512(token);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(packageName, token);
editor.apply();
}
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(packageName, hashedTokenWithSalt);
editor.apply();
String serverUrl;
String userId;
@ -206,8 +207,7 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
return result;
}
/// validate parameters
// validate parameters
try {
validateAccountType(account.type);
validateAuthTokenType(authTokenType);

View file

@ -57,7 +57,6 @@ import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
@ -65,7 +64,6 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
@ -80,7 +78,6 @@ import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
@ -98,6 +95,7 @@ public class EncryptionUtils {
public static final String MNEMONIC = "MNEMONIC";
public static final int ivLength = 16;
public static final int saltLength = 40;
public static final String HASH_DELIMITER = "$";
private static final String ivDelimiter = "fA=="; // "|" base64 encoded
private static final int iterationCount = 1024;
@ -132,9 +130,9 @@ public class EncryptionUtils {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
String privateKey)
throws IOException, NoSuchAlgorithmException, ShortBufferException, InvalidKeyException,
throws NoSuchAlgorithmException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
NoSuchProviderException, IllegalBlockSizeException, InvalidKeySpecException, CertificateException {
IllegalBlockSizeException, InvalidKeySpecException {
HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
@ -171,9 +169,9 @@ public class EncryptionUtils {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
String privateKey)
throws IOException, NoSuchAlgorithmException, ShortBufferException, InvalidKeyException,
throws NoSuchAlgorithmException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
NoSuchProviderException, IllegalBlockSizeException, CertificateException, InvalidKeySpecException {
IllegalBlockSizeException, InvalidKeySpecException {
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
@ -278,9 +276,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
throws NoSuchProviderException, NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException {
BadPaddingException, IllegalBlockSizeException, IOException {
File file = new File(ocFile.getStoragePath());
return encryptFile(file, encryptionKeyBytes, iv);
@ -294,9 +292,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
throws NoSuchProviderException, NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException {
BadPaddingException, IllegalBlockSizeException, IOException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
@ -325,9 +323,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag)
throws NoSuchProviderException, NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException {
BadPaddingException, IllegalBlockSizeException, IOException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
@ -370,9 +368,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String encryptStringAsymmetric(String string, String cert)
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, InvalidKeySpecException,
throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException,
CertificateException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
@ -406,9 +404,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String decryptStringAsymmetric(String string, String privateKeyString)
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, CertificateException,
throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException,
InvalidKeySpecException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
@ -437,10 +435,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String encryptStringSymmetric(String string, byte[] encryptionKeyBytes)
throws NoSuchProviderException, NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, InvalidKeySpecException,
CertificateException {
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
byte[] iv = randomBytes(ivLength);
@ -469,10 +466,9 @@ public class EncryptionUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
throws NoSuchProviderException, NoSuchAlgorithmException,
throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, CertificateException,
InvalidKeySpecException {
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
@ -502,8 +498,8 @@ public class EncryptionUtils {
* @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
*/
public static String encryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException, InvalidParameterSpecException {
NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
@ -533,7 +529,7 @@ public class EncryptionUtils {
* @return decrypted string
*/
public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException,
NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
// split up iv, salt
@ -559,7 +555,7 @@ public class EncryptionUtils {
.replace("-----END PRIVATE KEY-----", "");
}
public static String privateKeyToPEM(PrivateKey privateKey) throws IOException {
public static String privateKeyToPEM(PrivateKey privateKey) {
String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
@ -642,4 +638,56 @@ public class EncryptionUtils {
return iv;
}
/**
* Generate a SHA512 with appended salt
*
* @param token token to be hashed
* @return SHA512 with appended salt, delimiter HASH_DELIMITER
*/
public static String generateSHA512(String token) {
String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
return generateSHA512(token, salt);
}
/**
* Generate a SHA512 with appended salt
*
* @param token token to be hashed
* @return SHA512 with appended salt, delimiter HASH_DELIMITER
*/
public static String generateSHA512(String token, String salt) {
MessageDigest digest;
String hashedToken = "";
byte[] hash;
try {
digest = MessageDigest.getInstance("SHA-512");
digest.update(salt.getBytes());
hash = digest.digest(token.getBytes());
StringBuilder stringBuilder = new StringBuilder();
for (byte hashByte : hash) {
stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
}
stringBuilder.append(HASH_DELIMITER);
stringBuilder.append(salt);
hashedToken = stringBuilder.toString();
} catch (NoSuchAlgorithmException e) {
Log_OC.e(TAG, "Generating SHA512 failed", e);
}
return hashedToken;
}
public static boolean verifySHA512(String hashWithSalt, String compareToken) {
String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
String newHash = generateSHA512(compareToken, salt);
return hashWithSalt.equals(newHash);
}
}