데이터베이스에서 일반 텍스트 암호 암호화 / 해싱
나는 한 상속 난 그냥 SQL Server 데이터베이스에서 일반 텍스트로 30 만 사용자 이름 / 암호를 통해 매장을 발견했습니다하는 웹 응용 프로그램을. 이것이 Very Bad Thing ™이라는 것을 알고 있습니다.
암호화 / 암호 해독을 위해 로그인 및 암호 업데이트 프로세스를 업데이트해야하며 시스템의 나머지 부분에 미치는 영향을 최소화하면서 데이터베이스에서 일반 텍스트 암호를 제거하는 가장 좋은 방법으로 권장하는 것은 무엇입니까?
도움을 주시면 감사하겠습니다.
편집 : 명확하지 않은 경우 죄송합니다. 특정 암호화 / 해싱 방법이 아닌 암호를 암호화 / 해시하는 절차가 무엇인지 물어 보려고했습니다.
나는 단지 :
- DB 백업
- 로그인 / 업데이트 비밀번호 코드 업데이트
- 시간이 지나면 사용자 테이블의 모든 레코드를 살펴보고 암호를 해시하고 각 레코드를 바꿉니다.
- 사용자가 계속해서 비밀번호를 로그인 / 업데이트 할 수 있는지 테스트
내 걱정은 순전히 사용자 수가 많기 때문에 올바르게 수행하고 있는지 확인하고 싶습니다.
암호화 된 암호에 대한 열을 데이터베이스에 추가 한 다음 현재 암호를 가져 오는 모든 레코드에 대해 일괄 작업을 실행하고 암호화해야한다고 생각합니다 (다른 사람들이 md5와 같은 해시가 꽤 표준 편집 이라고 언급했듯이). 자체적으로 사용하십시오-좋은 토론을 위해 다른 답변을 참조하십시오 ), 새 열에 저장하고 모든 것이 원활하게 진행되었는지 확인하십시오.
그런 다음 로그인시 사용자가 입력 한 암호를 해시하도록 프런트 엔드를 업데이트하고 일반 텍스트 대 일반 텍스트를 확인하는 대신 저장된 해시와 비교하여 확인해야합니다.
결국 평문 암호를 모두 제거하기 전에 아무 일도 일어나지 않았는지 확인하기 위해 잠시 두 열을 모두 제자리에 두는 것이 현명한 것처럼 보입니다.
암호 변경 / 알림 요청과 같이 암호가 입력 될 때마다 코드가 변경되어야한다는 것을 잊지 마십시오. 물론 잊어 버린 암호를 이메일로 보낼 수있는 능력을 잃게 되겠지만 이것은 나쁜 것은 아닙니다. 대신 암호 재설정 시스템을 사용해야합니다.
편집 : 마지막으로 테스트 베드 보안 로그인 웹 사이트에서 처음 시도한 오류를 피하는 것이 좋습니다.
사용자 암호를 처리 할 때 해싱이 발생하는 위치를 고려하십시오. 제 경우에는 해시가 웹 서버에서 실행되는 PHP 코드로 계산되었지만 암호는 사용자 컴퓨터에서 일반 텍스트로 페이지로 전송되었습니다! 어쨌든 https 시스템 (유니 네트워크) 내부에 있었기 때문에 내가 작업하고 있던 환경에서 괜찮 았습니다. 그러나 현실 세계에서는 자바 스크립트 등을 사용하여 사용자 시스템을 떠나기 전에 암호를 해시 한 다음 사이트로 해시를 전송하고 싶을 것이라고 생각합니다.
편집 (2016) : 선호하는 순서대로 Argon2 , scrypt , bcrypt 또는 PBKDF2 를 사용합니다. 상황에 따라 가능한 한 큰 감속 요인을 사용하십시오. 검증 된 기존 구현을 사용합니다. 적절한 솔트를 사용하는지 확인하십시오 (사용중인 라이브러리가이를 확인해야하지만).
암호를 해시 할 때는 DO NOT USE PLAIN MD5를 사용하십시오 .
기본적으로 레인보우 테이블 공격 을 방지하기 위해 임의의 솔트를 사용하고 해싱 속도를 늦출 수있는 충분한 시간을 반복 ( 재해 싱 )하는 PBKDF2를 사용 합니다. 애플리케이션이 너무 오래 걸리지는 않지만 공격자가 무차별 대입 할 수 있습니다. 많은 수의 다른 암호가
문서에서 :
- 실행 가능한 반복 횟수를 확인하려면 구현 시간을 최소 1000 회 이상 반복하십시오.
- 8 바이트 (64 비트)의 솔트면 충분하며 임의의 솔트는 안전 할 필요가 없습니다 (솔트는 암호화되지 않았으므로 누군가가 추측 할 것이라고 걱정하지 않습니다).
- 해싱 할 때 솔트를 적용하는 좋은 방법은 암호를 HMAC 키로 사용하고 솔트를 해시 할 텍스트로 사용하여 선호하는 해시 알고리즘과 함께 HMAC를 사용하는 것입니다 ( 문서 의이 섹션 참조 ).
SHA-256을 보안 해시로 사용하는 Python의 구현 예 :
편집 : Eli Collins가 언급했듯이 이것은 PBKDF2 구현이 아닙니다. PassLib 와 같이 표준을 고수하는 구현을 선호해야합니다 .
from hashlib import sha256
from hmac import HMAC
import random
def random_bytes(num_bytes):
return "".join(chr(random.randrange(256)) for i in xrange(num_bytes))
def pbkdf_sha256(password, salt, iterations):
result = password
for i in xrange(iterations):
result = HMAC(result, salt, sha256).digest() # use HMAC to apply the salt
return result
NUM_ITERATIONS = 5000
def hash_password(plain_password):
salt = random_bytes(8) # 64 bits
hashed_password = pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)
# return the salt and hashed password, encoded in base64 and split with ","
return salt.encode("base64").strip() + "," + hashed_password.encode("base64").strip()
def check_password(saved_password_entry, plain_password):
salt, hashed_password = saved_password_entry.split(",")
salt = salt.decode("base64")
hashed_password = hashed_password.decode("base64")
return hashed_password == pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)
password_entry = hash_password("mysecret")
print password_entry # will print, for example: 8Y1ZO8Y1pi4=,r7Acg5iRiZ/x4QwFLhPMjASESxesoIcdJRSDkqWYfaA=
check_password(password_entry, "mysecret") # returns True
기본 전략은 키 파생 기능을 사용하여 약간의 솔트를 사용하여 암호를 "해시"하는 것입니다. 솔트 및 해시 결과는 데이터베이스에 저장됩니다. 사용자가 암호를 입력하면 솔트와 해당 입력이 동일한 방식으로 해시되고 저장된 값과 비교됩니다. 일치하면 사용자가 인증됩니다.
악마는 세부 사항에 있습니다. 첫째, 선택한 해시 알고리즘에 따라 많이 달라집니다. 해시 기반 메시지 인증 코드를 기반으로하는 PBKDF2와 같은 키 파생 알고리즘은 주어진 출력 (공격자가 데이터베이스에서 찾은 내용)을 생성 할 입력 (이 경우 암호)을 찾는 것을 "계산적으로 실행 불가능"하게 만듭니다. ).
사전 계산 된 사전 공격은 해시 출력에서 암호까지 사전 계산 된 색인 또는 사전을 사용합니다. 해싱은 느리기 때문에 (또는 그래야합니다) 공격자는 가능한 모든 암호를 한 번 해시하고 해시가 주어지면 해당 암호를 조회 할 수있는 방식으로 색인 된 결과를 저장합니다. 이것은 시간에 대한 공간의 고전적인 절충안입니다. 암호 목록이 방대 할 수 있기 때문에 (무지개 테이블과 같은) 트레이드 오프를 조정하는 방법이 있으므로 공격자가 많은 공간을 절약하기 위해 약간의 속도를 포기할 수 있습니다.
사전 계산 공격은 "암호화 솔트"를 사용하여 차단됩니다. 이것은 암호로 해시 된 일부 데이터입니다. 비밀 일 필요는 없으며 주어진 암호에 대해 예측할 수 없을뿐입니다. 각 salt 값에 대해 공격자는 새 사전이 필요합니다. 1 바이트의 솔트를 사용하는 경우 공격자는 각각 다른 솔트로 생성 된 256 개의 사전 사본이 필요합니다. 먼저 그는 솔트를 사용하여 올바른 사전을 찾은 다음 해시 출력을 사용하여 사용 가능한 암호를 찾습니다. 하지만 4 바이트를 더하면 어떨까요? 이제 그는 40 억 부의 사전이 필요합니다. 충분히 큰 솔트를 사용하면 사전 공격이 방지됩니다. 실제로 암호화 품질 난수 생성기의 8 ~ 16 바이트 데이터는 좋은 솔트가됩니다.
테이블에서 사전 계산을 수행하면 공격자는 각 시도에서 해시를 계산합니다. 이제 암호를 찾는 데 걸리는 시간은 전적으로 후보를 해시하는 데 걸리는 시간에 따라 다릅니다. 이 시간은 해시 함수의 반복으로 증가합니다. 반복 횟수는 일반적으로 키 유도 함수의 매개 변수입니다. 오늘날 많은 모바일 장치는 10,000에서 20,000 개의 반복을 사용하는 반면 서버는 100,000 개 이상을 사용할 수 있습니다. (bcrypt 알고리즘은 필요한 시간의 대수 척도 인 "비용 요소"라는 용어를 사용합니다.)
현재 암호 열을 잠시 유지하는 Xan의 조언 을 따르십시오. 이렇게하면 문제가 발생하면 빠르고 쉽게 롤백 할 수 있습니다.
비밀번호 암호화에 관해서는 :
- 소금을 사용하다
- 암호를 의미하는 해시 알고리즘을 사용합니다 (즉, 느립니다 ).
자세한 내용은 Thomas Ptacek의 Enough With The Rainbow Tables : 보안 암호 체계에 대해 알아야 할 사항을 참조하십시오.
다음을 수행해야한다고 생각합니다.
- HASHED_PASSWORD 또는 유사한 이름의 새 열을 작성하십시오.
- 두 열을 모두 확인하도록 코드를 수정하십시오.
- 해시되지 않은 테이블에서 해시 된 테이블로 점차적으로 암호를 마이그레이션합니다. 예를 들어, 사용자가 로그인하면 암호를 자동으로 해시 된 열로 마이그레이션하고 해시되지 않은 버전을 제거합니다. 새로 등록 된 모든 사용자는 해시 된 암호를 갖게됩니다.
- 시간이 지나면 한 번에 n 명의 사용자를 마이그레이션하는 스크립트를 실행할 수 있습니다.
- 해시되지 않은 암호가 더 이상 남아 있지 않으면 이전 암호 열을 제거 할 수 있습니다 (사용중인 데이터베이스에 따라 그렇게하지 못할 수 있음). 또한 이전 암호를 처리하기 위해 코드를 제거 할 수 있습니다.
- 끝났습니다!
그것은 몇 주 전에 제 문제였습니다. 대규모 MIS 프로젝트를 975 개의 서로 다른 지리적 위치에 배포하여 자체 사용자 자격 증명 저장소가 이미 구현되고 사용중인 여러 응용 프로그램 집합에 대한 인증 자로 사용될 것입니다. 이미 REST 및 SOAP 기반 인증 서비스를 모두 제공했지만 고객은 관련 테이블 또는보기의 읽기 전용보기에 대한 DB 연결만으로 다른 애플리케이션에서 사용자 자격 증명 저장소에 도달 할 수 있다고 주장했습니다. 한숨 ... (이 고도로 결합 된 잘못된 설계 결정은 또 다른 질문의 주제입니다).
그래서 우리는 솔트 처리되고 반복적으로 해시 된 암호 저장 체계를 사양으로 변환하고 쉽게 통합 할 수 있도록 몇 가지 다른 언어 구현을 제공해야했습니다.
우리는 이것을 Fairly Secure Hashed Passwords 또는 간단히 FSHP 라고 불렀습니다 . Python, Ruby, PHP5로 구현하여 Public Domain에 배포했습니다. http://github.com/bdd/fshp 에서 GitHub에서 소비, 분기, 불꽃 또는 침을 뱉을 수 있습니다.
FSHP는 솔트 처리되고 반복적으로 해시 된 암호 해싱 구현입니다.
설계 원칙은 RFC 2898의 PBKDF1 사양 (일명 : PKCS # 5 : 암호 기반 암호화 사양 버전 2.0)과 유사 합니다. FSHP를 사용하면 SHA-1 및 SHA-2 중에서 솔트 길이, 반복 횟수 및 기본 암호화 해시 함수를 선택할 수 있습니다 . (256, 384, 512). 모든 출력의 시작 부분에 자체 정의 메타 접두사를 사용하면 소비자가 자신의 암호 저장소 보안 기준을 선택할 수 있도록하면서 이식 가능합니다.
보안 :
Default FSHP1 uses 8 byte salts, with 4096 iterations of SHA-256 hashing. - 8 byte salt renders rainbow table attacks impractical by multiplying the required space with 2^64. - 4096 iterations causes brute force attacks to be fairly expensive. - There are no known attacks against SHA-256 to find collisions with a computational effort of fewer than 2^128 operations at the time of this release.
IMPLEMENTATIONS:
- Python: Tested with 2.3.5 (w/ hashlib), 2.5.1, 2.6.1
- Ruby : Tested with 1.8.6
- PHP5 : Tested with 5.2.6
Everyone is more than welcome to create missing language implementations or polish the current ones.
BASIC OPERATION (with Python):
>>> fsh = fshp.crypt('OrpheanBeholderScryDoubt')
>>> print fsh
{FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhzGLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g==
>>> fshp.validate('OrpheanBeholderScryDoubt', fsh)
True
CUSTOMIZING THE CRYPT:
Let's weaken our password hashing scheme. - Decrease the salt length from default 8 to 2. - Decrease the iteration round from default 4096 to 10. - Select FSHP0 with SHA-1 as the underlying hash algorithm.
>>> fsh = fshp.crypt('ExecuteOrder66', saltlen=2, rounds=10, variant=0)
>>> print fsh
{FSHP0|2|10}Nge7yRT/vueEGVFPIxcDjiaHQGFQaQ==
As the others mentioned, you don't want to decrypt if you can help it. Standard best practice is to encrypt using a one-way hash, and then when the user logs in hash their password to compare it.
Otherwise you'll have to use a strong encryption to encrypt and then decrypt. I'd only recommend this if the political reasons are strong (for example, your users are used to being able to call the help desk to retrieve their password, and you have strong pressure from the top not to change that). In that case, I'd start with encryption and then start building a business case to move to hashing.
For authentication purposes you should avoid storing the passwords using reversible encryption, i.e. you should only store the password hash and check the hash of the user-supplied password against the hash you have stored. However, that approach has a drawback: it's vulnerable to rainbow table attacks, should an attacker get hold of your password store database.
What you should do is store the hashes of a pre-chosen (and secret) salt value + the password. I.e., concatenate the salt and the password, hash the result, and store this hash. When authenticating, do the same - concatenate your salt value and the user-supplied password, hash, then check for equality. This makes rainbow table attacks unfeasible.
Of course, if the user send passwords across the network (for example, if you're working on a web or client-server application), then you should not send the password in clear text across, so instead of storing hash(salt + password) you should store and check against hash(salt + hash(password)), and have your client pre-hash the user-supplied password and send that one across the network. This protects your user's password as well, should the user (as many do) re-use the same password for multiple purposes.
- Encrypt using something like MD5, encode it as a hex string
- You need a salt; in your case, the username can be used as the salt (it has to be unique, the username should be the most unique value available ;-)
- use the old password field to store the MD5, but tag the MD5 (i.e.g "MD5:687A878....") so that old (plain text) and new (MD5) passwords can co-exist
- change the login procedure to verify against the MD5 if there is an MD5, and against the plain password otherwise
- change the "change password" and "new user" functions to create MD5'ed passwords only
- now you can run the conversion batch job, which might take as long as needed
- after the conversion has been run, remove the legacy-support
Step 1: Add encrypted field to database
Step 2: Change code so that when password is changed, it updates both fields but logging in still uses old field.
Step 3: Run script to populate all the new fields.
Step 4: Change code so that logging in uses new field and changing passwords stops updating old field.
Step 5: Remove unencrypted passwords from database.
This should allow you to accomplish the changeover without interruption to the end user.
Also: Something I would do is name the new database field something that is completely unrelated to password like "LastSessionID" or something similarly boring. Then instead of removing the password field, just populate with hashes of random data. Then, if your database ever gets compromised, they can spend all the time they want trying to decrypt the "password" field.
This may not actually accomplish anything, but it's fun thinking about someone sitting there trying to figure out worthless information
As with all security decisions, there are tradeoffs. If you hash the password, which is probably your easiest move, you can't offer a password retrieval function that returns the original password, nor can your staff look up a person's password in order to access their account.
You can use symmetric encryption, which has its own security drawbacks. (If your server is compromised, the symmetric encryption key may be compromised also).
You can use public-key encryption, and run password retrieval/customer service on a separate machine which stores the private key in isolation from the web application. This is the most secure, but requires a two-machine architecture, and probably a firewall in between.
I'm not a security expert, but i htink the current recommendation is to use bcrypt/blowfish or a SHA-2 variant, not MD5 / SHA1.
Probably you need to think in terms of a full security audit, too
MD5 and SHA1 have shown a bit of weakness (two words can result in the same hash) so using SHA256-SHA512 / iterative hashes is recommended to hash the password.
I would write a small program in the language that the application is written in that goes and generates a random salt that is unique for each user and a hash of the password. The reason I tend to use the same language as the verification is that different crypto libraries can do things slightly differently (i.e. padding) so using the same library to generate the hash and verify it eliminates that risk. This application could also then verify the login after the table has been updated if you want as it knows the plain text password still.
- Don't use MD5/SHA1
- Generate a good random salt (many crypto libraries have a salt generator)
- An iterative hash algorithm as orip recommended
- Ensure that the passwords are not transmitted in plain text over the wire
I would like to suggest one improvement to the great python example posted by Orip. I would redefine the random_bytes function to be:
def random_bytes(num_bytes):
return os.urandom(num_bytes)
Of course, you would have to import the os module. The os.urandom function provides a random sequence of bytes that can be safely used in cryptographic applications. See the reference help of this function for further details.
To hash the password you can use the HashBytes function. Returns a varbinary, so you'd have to create a new column and then delete the old varchar one.
Like
ALTER TABLE users ADD COLUMN hashedPassword varbinary(max);
ALTER TABLE users ADD COLUMN salt char(10);
--Generate random salts and update the column, after that
UPDATE users SET hashedPassword = HashBytes('SHA1',salt + '|' + password);
Then you modify the code to validate the password, using a query like
SELECT count(*) from users WHERE hashedPassword =
HashBytes('SHA1',salt + '|' + <password>)
where <password> is the value entered by the user.
hash them with md5. that's what is usually done with passwords.
참고URL : https://stackoverflow.com/questions/287517/encrypting-hashing-plain-text-passwords-in-database
'Nice programing' 카테고리의 다른 글
| `this-> field` 대신`this-field` 작성에 대해 경고하는 GCC 옵션이 있습니까? (0) | 2020.11.27 |
|---|---|
| 웹 사이트 스크린 샷을 만드는 명령 줄 프로그램 (Linux) (0) | 2020.11.27 |
| SQL Server Express로 매일 백업을 예약하려면 어떻게해야합니까? (0) | 2020.11.27 |
| Scala : Seq를 var-args 함수에 전달 (0) | 2020.11.27 |
| 목록을 문자열로 변환 (0) | 2020.11.27 |