Cập nhật băm mật khẩu mà không buộc mật khẩu mới cho người dùng hiện tại


32

Bạn duy trì một ứng dụng hiện có với một cơ sở người dùng được thiết lập. Theo thời gian, người ta đã quyết định rằng kỹ thuật băm mật khẩu hiện tại đã lỗi thời và cần được nâng cấp. Hơn nữa, vì lý do UX, bạn không muốn người dùng hiện tại bị buộc phải cập nhật mật khẩu của họ. Toàn bộ cập nhật băm mật khẩu cần phải xảy ra đằng sau màn hình.

Giả sử mô hình cơ sở dữ liệu 'đơn giản hóa' cho người dùng có chứa:

  1. ID
  2. E-mail
  3. Mật khẩu

Làm thế nào để một người đi xung quanh để giải quyết một yêu cầu như vậy?


Suy nghĩ hiện tại của tôi là:

  • tạo một phương thức băm mới trong lớp thích hợp
  • cập nhật bảng người dùng trong cơ sở dữ liệu để giữ trường mật khẩu bổ sung
  • Khi người dùng đăng nhập thành công bằng cách sử dụng hàm băm mật khẩu lỗi thời, hãy điền vào trường mật khẩu thứ hai bằng hàm băm được cập nhật

Điều này cho tôi một vấn đề là tôi không thể phân biệt hợp lý giữa người dùng có và những người chưa cập nhật hàm băm mật khẩu của họ và do đó sẽ buộc phải kiểm tra cả hai. Điều này có vẻ thiếu sót khủng khiếp.

Hơn nữa, điều này về cơ bản có nghĩa là kỹ thuật băm cũ có thể bị buộc phải ở lại vô thời hạn cho đến khi mỗi người dùng duy nhất cập nhật mật khẩu của họ. Chỉ tại thời điểm đó, tôi mới có thể bắt đầu loại bỏ kiểm tra băm cũ và loại bỏ trường cơ sở dữ liệu thừa.

Tôi chủ yếu tìm kiếm một số mẹo thiết kế ở đây, vì 'giải pháp' hiện tại của tôi là bẩn, không đầy đủ và không, nhưng nếu mã thực tế là bắt buộc để mô tả một giải pháp có thể, hãy sử dụng bất kỳ ngôn ngữ nào.


4
Tại sao bạn không thể phân biệt giữa một tập hợp và một băm thứ cấp chưa đặt? Chỉ cần làm cho cột cơ sở dữ liệu nullable và kiểm tra null.
Kilian Foth

6
Đi với một kiểu băm là cột mới của bạn, thay vì một trường cho hàm băm mới. Khi bạn cập nhật hàm băm, thay đổi trường loại. Theo cách đó, khi điều này xảy ra một lần nữa trong tương lai, bạn sẽ ở vào vị trí để đối phó và sẽ không có cơ hội bạn giữ một hàm băm cũ (có lẽ kém an toàn hơn).
Michael Kohne

1
Tôi nghĩ rằng bạn đang đi đúng hướng. 6 tháng hoặc một năm kể từ bây giờ, bạn có thể buộc bất kỳ người dùng còn lại nào thay đổi mật khẩu của họ. Một năm sau hoặc bất cứ điều gì, bạn có thể thoát khỏi lĩnh vực cũ.
GlenPeterson

3
Tại sao không chỉ băm băm cũ?
Siyuan Ren

bởi vì bạn muốn mật khẩu được băm (không phải là băm cũ) để việc băm mật khẩu đó (tức là so sánh nó với mật khẩu được băm do người dùng cung cấp) cung cấp cho bạn ... mật khẩu - không phải là một giá trị khác mà sau đó cần phải được "khử" theo phương pháp cũ. Có thể nhưng không sạch hơn.
Michael Durrant

Câu trả lời:


25

Tôi sẽ đề nghị thêm một trường mới, "hash_method", có lẽ là 1 để biểu thị phương thức cũ và 2 để biểu thị phương thức mới.

Nói một cách hợp lý, nếu bạn quan tâm đến loại điều này và ứng dụng của bạn tồn tại khá lâu (mà dường như nó đã tồn tại), điều này có thể sẽ xảy ra một lần nữa vì mật mã và bảo mật thông tin là một lĩnh vực phát triển, khá khó lường. Đã có lúc một lần chạy đơn giản thông qua MD5 là tiêu chuẩn, nếu băm được sử dụng! Sau đó, người ta có thể nghĩ rằng họ nên sử dụng SHA1, và bây giờ có muối, muối toàn cầu + muối ngẫu nhiên riêng lẻ, SHA3, các phương pháp tạo số ngẫu nhiên sẵn sàng cho tiền điện tử khác nhau ... điều này sẽ không chỉ là 'dừng lại', vì vậy bạn có thể cũng sửa lỗi này theo cách có thể mở rộng, có thể lặp lại.

Vì vậy, giả sử bây giờ bạn có một cái gì đó giống như (trong pseudo-javascript để đơn giản, tôi hy vọng):

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword());


if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword)
{
    // TODO: Learn what "hash" means
    return clearPassword + "H@$I-I";
}

Bây giờ nhận ra có một phương pháp tốt hơn, bạn chỉ cần đưa ra một cấu trúc lại nhỏ:

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword(), user.getHashingMethod());

if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword, hashMethod)
{
    // Note: Hash doesn't mean what we thought it did. Oops...

    var hash;
    if (hashMethod == 1)
    {
        hash = clearPassword + "H@$I-I";
    }
    else if (hashMethod == 2)
    {
        // Totally gonna get it right this time.
        hash = SuperMethodTheNSASaidWasAwesome(clearPassword);
    }
    return hash;
}

Không có tác nhân bí mật hoặc lập trình viên nào bị tổn hại trong quá trình sản xuất câu trả lời này.


+1 Điều này có vẻ như là một trừu tượng hợp lý, cảm ơn. Mặc dù cuối cùng tôi có thể di chuyển các trường băm và hàm băm sang một bảng riêng khi tôi thực hiện điều này.
Willem

1
Vấn đề với kỹ thuật này là đối với các tài khoản không đăng nhập, bạn không thể cập nhật giá trị băm. Theo kinh nghiệm của tôi, hầu hết người dùng sẽ không đăng nhập trong nhiều năm (trừ khi bạn xóa người dùng không hoạt động). Sự trừu tượng của bạn cũng không thực sự hiệu quả, vì nó không tính đến muối. Sự trừu tượng hóa tiêu chuẩn có hai chức năng, một để xác minh và một để tạo.
CodeInChaos

Băm mật khẩu hầu như không phát triển trong 15 năm qua. Bcrypt đã được xuất bản vào năm 1999 và vẫn còn trong số các băm được khuyến nghị. Chỉ có một cải tiến đáng kể đã xảy ra kể từ đó: băm liên tục bộ nhớ cứng, được tiên phong bởi tiền điện tử.
CodeInChaos

Điều này làm cho các băm cũ dễ bị tổn thương (ví dụ: nếu ai đó đánh cắp DB). Băm từng hàm băm cũ bằng phương pháp mới an toàn hơn sẽ giảm thiểu điều này ở một mức độ nào đó (bạn vẫn sẽ cần loại băm để phân biệt giữa newHash(oldHash, salt)hoặcnewHash(password, salt)
dbkk

42

Nếu bạn quan tâm đầy đủ về việc triển khai chương trình băm mới cho tất cả người dùng càng nhanh càng tốt (ví dụ: vì chương trình cũ thực sự không an toàn), thực sự có một cách để "di chuyển" tức thời mọi mật khẩu.

Ý tưởng về cơ bản là để băm băm . Thay vì chờ người dùng cung cấp mật khẩu hiện tại ( p) cho lần đăng nhập tiếp theo, bạn ngay lập tức sử dụng thuật toán băm mới ( H2) trên hàm băm hiện có đã được tạo bởi thuật toán cũ ( H1):

hash = H2(hash)  # where hash was previously equal to H1(p) 

Sau khi thực hiện chuyển đổi đó, bạn vẫn hoàn toàn có thể thực hiện xác minh mật khẩu; bạn chỉ cần tính toán H2(H1(p'))thay vì trước đó H1(p')bất cứ khi nào người dùng cố gắng đăng nhập bằng mật khẩu p'.

Về lý thuyết, kỹ thuật này có thể được áp dụng cho nhiều di cư ( H3, H4, vv). Trong thực tế, bạn sẽ muốn thoát khỏi hàm băm cũ, cả vì lý do hiệu suất và khả năng đọc. May mắn thay, điều này khá dễ dàng: khi đăng nhập thành công tiếp theo, chỉ cần tính toán hàm băm mới của mật khẩu người dùng và thay thế hàm băm hiện có bằng nó:

hash = H2(p)

Bạn cũng sẽ cần cột bổ sung để ghi nhớ hàm băm nào bạn đang lưu trữ: một trong những mật khẩu hoặc của hàm băm cũ. Trong trường hợp không may bị rò rỉ cơ sở dữ liệu, cột này không nên làm cho công việc của cracker trở nên dễ dàng hơn; Bất kể giá trị của nó là bao nhiêu, kẻ tấn công vẫn sẽ phải đảo ngược H2thuật toán bảo mật hơn là cũ H1.


3
Một người khổng lồ +1: H2 (H1 (p)) thường an toàn như sử dụng H2 (p) trực tiếp, ngay cả khi H1 là khủng khiếp, bởi vì (1) muối và kéo dài được H2 chăm sóc và (2) các vấn đề với băm "bị hỏng" như MD5 không ảnh hưởng đến băm mật khẩu. Liên quan: crypto.stackexchange.com/questions/2945/ từ
orip

Thật thú vị, tôi đã nghĩ rằng băm băm cũ sẽ tạo ra một số 'vấn đề' bảo mật. Có vẻ như tôi đã nhầm.
Willem

4
Nếu H1 thực sự khủng khiếp thì điều này sẽ không an toàn. Trở nên khủng khiếp trong bối cảnh này chủ yếu là về việc mất rất nhiều entropy từ đầu vào. Ngay cả MD4 và MD5 cũng gần như hoàn hảo về vấn đề này, vì vậy cách tiếp cận này chỉ không an toàn đối với một số băm homebrew hoặc băm có đầu ra thực sự ngắn (dưới 80 bit).
CodeInChaos

1
Trong trường hợp đó, có lẽ tùy chọn duy nhất của bạn là thừa nhận rằng bạn đã làm khó và chỉ cần tiếp tục và đặt lại mật khẩu cho tất cả người dùng. Đó là ít về di cư tại thời điểm đó và nhiều hơn về kiểm soát thiệt hại.
Xion

8

Giải pháp của bạn (cột bổ sung trong cơ sở dữ liệu) là hoàn toàn chấp nhận được. Vấn đề duy nhất với nó là vấn đề bạn đã đề cập: băm cũ vẫn được sử dụng cho người dùng chưa xác thực kể từ khi thay đổi.

Để tránh tình trạng này, bạn có thể đợi người dùng tích cực nhất của mình chuyển sang thuật toán băm mới, sau đó:

  1. Xóa tài khoản của những người dùng không xác thực quá lâu. Không có gì sai trong việc xóa tài khoản của một người dùng không bao giờ đến trang web trong ba năm.

  2. Gửi email cho những người dùng còn lại trong số những người chưa xác thực được một thời gian, nói rằng họ có thể quan tâm đến điều gì đó mới. Nó sẽ giúp giảm thêm số lượng tài khoản sử dụng băm cũ.

    Hãy cẩn thận: nếu bạn có tùy chọn không spam, người dùng có thể kiểm tra trên trang web của bạn, đừng gửi email cho người dùng đã kiểm tra nó.

  3. Cuối cùng, một vài tuần sau bước 2, hủy mật khẩu cho người dùng vẫn đang sử dụng kỹ thuật cũ. Khi nào và nếu họ cố gắng xác thực, họ sẽ thấy rằng mật khẩu của họ không hợp lệ; nếu họ vẫn quan tâm đến dịch vụ của bạn, họ có thể đặt lại.


1

Bạn có thể sẽ không bao giờ có nhiều hơn một vài loại băm, vì vậy bạn có thể giải quyết điều này mà không cần mở rộng cơ sở dữ liệu của bạn.

Nếu các phương thức có độ dài băm khác nhau và độ dài băm của mỗi phương thức là không đổi (giả sử, chuyển từ md5 sang hmac-sha1), bạn có thể cho biết phương thức từ độ dài băm. Nếu chúng có cùng độ dài, bạn có thể tính toán hàm băm bằng phương thức mới trước, sau đó là phương thức cũ nếu thử nghiệm đầu tiên thất bại. Nếu bạn có một trường (không hiển thị) cho biết khi nào người dùng được cập nhật / tạo lần cuối, bạn có thể sử dụng đó làm chỉ báo cho phương pháp nào sẽ sử dụng.


1

Tôi đã làm một cái gì đó như thế này và có một cách để biết nếu đó là hàm băm mới VÀ làm cho nó an toàn hơn nữa. Tôi đoán an toàn hơn là một điều tốt cho bạn nếu bạn dành thời gian để cập nhật hàm băm của mình ;-)

Thêm một cột mới vào bảng DB của bạn được gọi là "salt" và tốt, sử dụng nó như một muối được tạo ngẫu nhiên cho mỗi người dùng. (khá nhiều ngăn chặn các cuộc tấn công bảng cầu vồng)

Theo cách đó, nếu mật khẩu của tôi là "pass123" và muối ngẫu nhiên là 3333 thì mật khẩu mới của tôi sẽ là hàm băm của "pass1233333".

Nếu người dùng có muối bạn biết đó là băm mới. Nếu muối của người dùng là null, bạn biết đó là hàm băm cũ.


0

Cho dù chiến lược di chuyển của bạn tốt đến đâu, nếu cơ sở dữ liệu đã bị đánh cắp (và bạn không thể chứng minh một cách tiêu cực rằng điều này chưa bao giờ xảy ra), thì mật khẩu được lưu trữ với các hàm băm không an toàn có thể đã bị xâm phạm. Giảm thiểu duy nhất cho điều này là yêu cầu người dùng thay đổi mật khẩu của họ.

Đây không phải là một vấn đề có thể được giải quyết (chỉ) bằng cách viết mã. Giải pháp là thông báo cho người dùng và khách hàng về những rủi ro mà họ đã gặp phải. Khi bạn sẵn sàng cởi mở về nó, bạn có thể thực hiện di chuyển bằng cách đặt lại mật khẩu của mọi người dùng, vì đó là giải pháp dễ dàng và an toàn nhất. Tất cả các giải pháp thay thế thực sự là về việc che giấu sự thật rằng bạn đã làm hỏng việc.


1
Một thỏa hiệp mà tôi đã làm trong quá khứ là để giữ các mật khẩu cũ cho một khoảng thời gian, xào xáo lại họ như những người đăng nhập, nhưng sau đó sau thời gian đó đã trôi qua bạn sau đó buộc một thiết lập lại mật khẩu trên bất cứ ai không đăng nhập trong thời điểm đó Vì vậy, bạn có một khoảng thời gian mà bạn vẫn có mật khẩu kém an toàn hơn, nhưng thời gian bị hạn chế và bạn cũng giảm thiểu sự gián đoạn cho những người dùng đăng nhập thường xuyên.
Sean Burton
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.