public virtual void SetPassword( TAccount userAccount, string newPassword, string oldPassword = null, string nameOfExpensiveHashFunctionToUse = null, int?numberOfIterationsToUseForPhase1Hash = null) { byte[] oldPasswordHashPhase1 = oldPassword == null ? null : ComputePhase1Hash(userAccount, oldPassword); if (nameOfExpensiveHashFunctionToUse != null) { userAccount.PasswordHashPhase1FunctionName = nameOfExpensiveHashFunctionToUse; } if (numberOfIterationsToUseForPhase1Hash.HasValue) { userAccount.NumberOfIterationsToUseForPhase1Hash = numberOfIterationsToUseForPhase1Hash.Value; } byte[] newPasswordHashPhase1 = ComputePhase1Hash(userAccount, newPassword); userAccount.PasswordHashPhase2 = ComputePhase2HashFromPhase1Hash(userAccount, newPasswordHashPhase1); if (oldPassword != null && ComputePhase2HashFromPhase1Hash(userAccount, oldPasswordHashPhase1) == userAccount.PasswordHashPhase2) { try { if (userAccount.EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 == null) { using (Encryption.IPrivateKey accountLogKey = Encryption.DecryptAesCbcEncryptedPrivateKey( userAccount.EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1, oldPasswordHashPhase1)) { if (accountLogKey != null) { SetAccountLogKey(userAccount, accountLogKey, newPasswordHashPhase1); } return; } } } catch (Exception) { } } using (Encryption.IPrivateKey newPrivateKey = Encryption.GenerateNewPrivateKey()) { SetAccountLogKey(userAccount, newPrivateKey, newPasswordHashPhase1); } }
public virtual void SetAccountLogKey( TAccount userAccount, Encryption.IPrivateKey accountLogKey, byte[] phase1HashOfCorrectPassword) { //userAccount.EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1 = Encryption.EncryptPrivateKeyWithAesCbc(ecAccountLogKey, // phase1HashOfCorrectPassword); //using (ECDiffieHellmanPublicKey publicKey = accountLogKey.PublicKey) //{ // userAccount.EcPublicAccountLogKey = publicKey.ToByteArray(); //} }
/// <summary> /// </summary> /// <param name="privateKey">The private key that can be used to decrypt the value.</param> /// <returns>The decrypted value.</returns> public byte[] Read(Encryption.IPrivateKey privateKey) { if (string.IsNullOrEmpty(Ciphertext)) { throw new MemberAccessException("Cannot decrypt a value that has not been written."); } EcEncryptedMessageAesCbcHmacSha256 messageDeserializedFromJson = JsonConvert.DeserializeObject <EcEncryptedMessageAesCbcHmacSha256>(Ciphertext); return(messageDeserializedFromJson.Decrypt(privateKey)); }
/// <summary> /// Decrypt the message by providing the recipient's private EC key. /// </summary> /// <param name="recipientsPrivateEcKey">The private EC key matching the public key provided for encryption.</param> /// <returns>The decrypted message as a byte array</returns> public byte[] Decrypt(Encryption.IPrivateKey recipientsPrivateEcKey) { return(this.EncryptedMessage); //byte[] sessionKey; //try //{ // using (CngKey otherPartiesPublicKey = CngKey.Import(PublicOneTimeEcKey, CngKeyBlobFormat.EccPublicBlob)) // { // sessionKey = recipientsPrivateEcKey.DeriveKeyMaterial(otherPartiesPublicKey); // } //} //catch (CryptographicException e) //{ // throw new Exception("Failed to Decrypt log entry", e); //} //return Encryption.DecryptAesCbc(EncryptedMessage, sessionKey, checkAndRemoveHmac: true); }
protected void AdjustBlockingScoreForPastTyposTreatedAsFullFailures( IpHistory clientsIpHistory, IUserAccountController <TUserAccount> accountController, TUserAccount account, DateTime whenUtc, string correctPassword, byte[] phase1HashOfCorrectPassword) { double credit = 0d; if (clientsIpHistory == null) { return; } LoginAttemptSummaryForTypoAnalysis[] recentPotentialTypos = clientsIpHistory.RecentPotentialTypos.MostRecentFirst.ToArray(); Encryption.IPrivateKey privateAccountLogKey = null; try { foreach (LoginAttemptSummaryForTypoAnalysis potentialTypo in recentPotentialTypos) { bool usernameCorrect = potentialTypo.UsernameOrAccountId == account.UsernameOrAccountId; if (usernameCorrect) { string passwordSubmittedInFailedAttempt = null; if (account.GetType().Name == "SimulatedUserAccount") { passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Ciphertext; } else { if (privateAccountLogKey == null) { try { privateAccountLogKey = accountController.DecryptPrivateAccountLogKey(account, phase1HashOfCorrectPassword); } catch (Exception) { return; } } try { passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Read(privateAccountLogKey); } catch (Exception) { } } if (passwordSubmittedInFailedAttempt != null) { bool passwordHadTypo = EditDistance.Calculate(passwordSubmittedInFailedAttempt, correctPassword) <= _options.MaxEditDistanceConsideredATypo; if (passwordHadTypo) { credit += potentialTypo.Penalty.GetValue(_options.AccountCreditLimitHalfLife, whenUtc) * (1d - _options.PenaltyMulitiplierForTypo); } } } clientsIpHistory.RecentPotentialTypos.Remove(potentialTypo); } clientsIpHistory.CurrentBlockScore.SubtractInPlace(account.CreditHalfLife, credit, whenUtc); } finally { privateAccountLogKey?.Dispose(); } }
/// <summary> /// Sets the password of a user. /// <b>Important</b>: this does not authenticate the user but assumes the user has already been authenticated. /// The <paramref name="oldPassword"/> field is used only to optionally recover the EC symmetricKey, not to authenticate the user. /// </summary> /// <param name="userAccount"></param> /// <param name="newPassword">The new password to set.</param> /// <param name="oldPassword">If this optional field is provided and correct, the old password will allow us to re-use the old log decryption symmetricKey. /// <b>Providing this parameter will not cause this function to authenticate the user first. The caller must do so beforehand.</b></param> /// <param name="nameOfExpensiveHashFunctionToUse">The name of the phase 1 (expenseive) hash to use.</param> /// <param name="numberOfIterationsToUseForPhase1Hash">The number of iterations that the hash should be performed.</param> public virtual void SetPassword( TAccount userAccount, string newPassword, string oldPassword = null, string nameOfExpensiveHashFunctionToUse = null, int?numberOfIterationsToUseForPhase1Hash = null) { // Calculate the old passwords' phase 1 hash before making any changes to the hash function name or number of iterations. byte[] oldPasswordHashPhase1 = oldPassword == null ? null : ComputePhase1Hash(userAccount, oldPassword); // If the caller also wants to change the hash function or the number of iterations, // make that change here now that we're done hashing the old password and are about to hash the new one. if (nameOfExpensiveHashFunctionToUse != null) { userAccount.PasswordHashPhase1FunctionName = nameOfExpensiveHashFunctionToUse; } if (numberOfIterationsToUseForPhase1Hash.HasValue) { userAccount.NumberOfIterationsToUseForPhase1Hash = numberOfIterationsToUseForPhase1Hash.Value; } // Calculate the Phase1 hash, which is a computationally-heavy hash of the password // We will use this for encrypting the EC userAccount log symmetricKey. byte[] newPasswordHashPhase1 = ComputePhase1Hash(userAccount, newPassword); // Calculate the Phase2 hash by hasing the phase 1 hash with SHA256. // We can store this without revealing the phase 1 hash used to encrypt the EC userAccount log symmetricKey. // We can use it to verify whether a provided password is correct userAccount.PasswordHashPhase2 = ComputePhase2HashFromPhase1Hash(userAccount, newPasswordHashPhase1); // Store the ecAccountLogKey encrypted encrypted using symmetric encryption with the phase 1 password hash as its key. // First try using the ecAccountLogKey from the prior password if (oldPassword != null && ComputePhase2HashFromPhase1Hash(userAccount, oldPasswordHashPhase1) == userAccount.PasswordHashPhase2) { // If we have a valid old password, Decrypt the private log decryption symmetricKey so we can re-encrypt it // with the new password and continue to use it on future logins. try { using (Encryption.IPrivateKey accountLogKey = Encryption.DecryptAesCbcEncryptedPrivateKey( userAccount.EcPrivateAccountLogKeyEncryptedWithPasswordHashPhase1, oldPasswordHashPhase1)) { SetAccountLogKey(userAccount, accountLogKey, newPasswordHashPhase1); return; } } catch (Exception) { // Ignore crypto failures. They just mean we were unsuccessful in decrypting the symmetricKey and should create a new one. } } // We were unable to use an old EC Account Log Key, // so we'll create a new one using (Encryption.IPrivateKey newPrivateKey = Encryption.GenerateNewPrivateKey()) { SetAccountLogKey(userAccount, newPrivateKey, newPasswordHashPhase1); } }
/// <summary> /// Set the EC account log key /// </summary> /// <param name="userAccount"></param> /// <param name="accountLogKey"></param> /// <param name="phase1HashOfCorrectPassword">The phase 1 hash of the correct password</param> /// <returns></returns> public virtual void SetAccountLogKey( SimulatedUserAccount userAccount, Encryption.IPrivateKey accountLogKey, byte[] phase1HashOfCorrectPassword) { }
/// <summary> /// This analysis will examine the client IP's previous failed attempts to login to this account /// to determine if any failed attempts were due to typos. /// </summary> /// <param name="clientsIpHistory">Records of this client's previous attempts to examine.</param> /// <param name="accountController"></param> /// <param name="account">The account that the client is currently trying to login to.</param> /// <param name="whenUtc"></param> /// <param name="correctPassword">The correct password for this account. (We can only know it because /// the client must have provided the correct one this loginAttempt.)</param> /// <param name="phase1HashOfCorrectPassword">The phase1 hash of that correct password (which we could /// recalculate from the information in the previous parameters, but doing so would be expensive.)</param> /// <returns></returns> protected void AdjustBlockingScoreForPastTyposTreatedAsFullFailures( IpHistory clientsIpHistory, IUserAccountController <TUserAccount> accountController, TUserAccount account, DateTime whenUtc, string correctPassword, byte[] phase1HashOfCorrectPassword) { double credit = 0d; if (clientsIpHistory == null) { return; } LoginAttemptSummaryForTypoAnalysis[] recentPotentialTypos = clientsIpHistory.RecentPotentialTypos.MostRecentFirst.ToArray(); Encryption.IPrivateKey privateAccountLogKey = null; try { foreach (LoginAttemptSummaryForTypoAnalysis potentialTypo in recentPotentialTypos) { bool usernameCorrect = potentialTypo.UsernameOrAccountId == account.UsernameOrAccountId; //bool usernameHadTypo = !usernameCorrect && // EditDistance.Calculate(potentialTypo.UsernameOrAccountId, account.UsernameOrAccountId) <= //_options.MaxEditDistanceConsideredATypo; if (usernameCorrect) // || usernameHadTypo { // Get the plaintext password from the previous login attempt string passwordSubmittedInFailedAttempt = null; if (account.GetType().Name == "SimulatedUserAccount") { passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Ciphertext; } else { if (privateAccountLogKey == null) { // Get the EC decryption key, which is stored encrypted with the Phase1 password hash try { privateAccountLogKey = accountController.DecryptPrivateAccountLogKey(account, phase1HashOfCorrectPassword); } catch (Exception) { // There's a problem with the key that prevents us from decrypting it. We won't be able to do this analysis. return; } } // Now try to decrypt the incorrect password from the previous attempt and perform the typo analysis try { // Attempt to decrypt the password. passwordSubmittedInFailedAttempt = potentialTypo.EncryptedIncorrectPassword.Read(privateAccountLogKey); } catch (Exception) { // An exception is likely due to an incorrect key (perhaps outdated). // Since we simply can't do anything with a record we can't Decrypt, we carry on // as if nothing ever happened. No. Really. Nothing to see here. } } if (passwordSubmittedInFailedAttempt != null) { //bool passwordCorrect = passwordSubmittedInFailedAttempt == correctPassword; bool passwordHadTypo = // !passwordCorrect && EditDistance.Calculate(passwordSubmittedInFailedAttempt, correctPassword) <= _options.MaxEditDistanceConsideredATypo; // Get credit for this nearly-correct attempt to counter penalty if (passwordHadTypo) { credit += potentialTypo.Penalty.GetValue(_options.AccountCreditLimitHalfLife, whenUtc) * (1d - _options.PenaltyMulitiplierForTypo); } } } // Now that we know whether this past event was a typo or not, we no longer need to keep track // of it (and we should remove it so we don't double credit it). clientsIpHistory.RecentPotentialTypos.Remove(potentialTypo); } // Remove the amount to be credited from the block score due to the discovery of typos clientsIpHistory.CurrentBlockScore.SubtractInPlace(account.CreditHalfLife, credit, whenUtc); } finally { privateAccountLogKey?.Dispose(); } }
public new T Read(Encryption.IPrivateKey privateKey) => JsonConvert.DeserializeObject <T>(base.Read(privateKey));
public new string Read(Encryption.IPrivateKey privateKey) => Encoding.UTF8.GetString(base.Read(privateKey));
public byte[] Decrypt(Encryption.IPrivateKey recipientsPrivateEcKey) { return(this.EncryptedMessage); }