# 키체인
<문제점>
1. **kSecClassInternetPassword
Keychain에 저장된 인터넷 비밀번호를 찾으려고 시도하기때문에 문제가 생김**
2. add해주기전에 삭제하지않으면 Keychain에 같은 키를 사용하여 두 개 이상의 비밀번호가 저장되는 경우가 발생할 수 있음. 새 비밀번호 생성 전에 삭제
3. 기존 코드의 로그인 버튼 클릭 시, kSecAttrAccount를 사용하여 계정 정보를 가져오려고 시도했는데. 이 경우에는 계정 정보가 필요하지 않아서.수정된 코드에서는 kSecAttrService를 사용하여 저장된 비밀번호를 찾도록 변경!
→ 이렇게 하면 동일한 서비스를 사용하는 모든 계정의 비밀번호를 검색할 수 있음!, 그리고 우리 코드에서는 account가 필요없음 !
수정 전 코드
```swift
guard let existingItem = item as? [String : Any],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8),
let account = existingItem[kSecAttrAccount as String] as? String
else { return }
```
수정 후 코드
```swift
guard let existingItem = item as? [String : Any],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8)
else { return }
```
### 전체코드
```swift
class LogInViewController: UIViewController {
@IBOutlet weak var pwTextField: UITextField!
var diaryViewController: DiaryViewController?
override func viewDidLoad() {
super.viewDidLoad()
diaryViewController = self.storyboard?.instantiateViewController(withIdentifier: "diary") as? DiaryViewController
}
@IBAction func tapLogInButton(_ sender: Any) {
guard let diaryViewController = diaryViewController else { return }
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "passwordKey",
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { return }
guard status == errSecSuccess else { return }
guard let existingItem = item as? [String : Any],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8)
else { return }
guard pwTextField.text == password else {
let alert = UIAlertController(title: "비밀번호가 불일치", message: "등록된 비밀번호와 입력한 비밀번호가 일치하지 않습니다.", preferredStyle: .alert)
let confirm = UIAlertAction(title: "돌아가기", style: .default)
alert.addAction(confirm)
present(alert, animated: true, completion: nil)
return
}
diaryViewController.modalPresentationStyle = .fullScreen
present(diaryViewController, animated: true)
}
@IBAction func addNewPassword(_ sender: Any) {
let passwordRegex = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}"
let validPw = pwTextField.text?.range(of: passwordRegex, options: .regularExpression) != nil
if validPw {
guard let newPassword = pwTextField.text, !newPassword.isEmpty else { return }
let passwordData = newPassword.data(using: String.Encoding.utf8)!
// 기존 비밀번호를 삭제하고 추가해줘야항
let deleteQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "passwordKey"]
SecItemDelete(deleteQuery as CFDictionary)
// 추가
let addQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "passwordKey",
kSecValueData as String: passwordData]
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
guard addStatus == errSecSuccess else { return }
let alert = UIAlertController(title: "성공", message: "비밀번호가 성공적으로 등록되었습니다.", preferredStyle: .alert)
let confirm = UIAlertAction(title: "확인", style: .default)
alert.addAction(confirm)
present(alert, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "비밀번호 오류", message: "영어 대소문자, 숫자, 특수기호를 넣어 8자리 이상 16자리 이하 비밀번호를 만들어주세요.", preferredStyle: .alert)
let confirm = UIAlertAction(title: "확인", style: .default)
alert.addAction(confirm)
present(alert, animated: true, completion: nil)
}
}
}
```