HTTPS 통신 구현
iOS 앱에서 네트워크 통신 시 보안을 강화하기 위해 HTTPS 프로토콜 사용은 필수적입니다. Info.plist 파일에 다음 설정을 추가하여 ATS(App Transport Security)를 활성화합니다.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
SSL 인증서 고정 구현
서버 인증서를 직접 검증하여 위조된 서버로의 연결을 차단하는 SSL Pinning 기술을 적용할 수 있습니다.
import Foundation
class NetworkSecurityDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust,
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let serverCertData = SecCertificateCopyData(certificate) as Data
guard let bundledCertPath = Bundle.main.path(forResource: "server_certificate", ofType: "der"),
let bundledCertData = try? Data(contentsOf: URL(fileURLWithPath: bundledCertPath)),
serverCertData == bundledCertData else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
}
}
// 활용 예시
let requestURL = URL(string: "https://api.secure-service.com")!
let secureSession = URLSession(configuration: .default, delegate: NetworkSecurityDelegate(), delegateQueue: nil)
let dataTask = secureSession.dataTask(with: requestURL) { data, response, error in
// 응답 처리 로직
}
dataTask.resume()
SQL Injection 방지 처리
사용자 입력값을 처리할 때는 반드시 파라미터화된 쿼리를 사용하여 SQL Injection 공격을 방지해야 합니다.
import SQLite3
func executeSecureQuery(inputValue: String) {
var database: OpaquePointer?
guard sqlite3_open(databasePath, &database) == SQLITE_OK else { return }
var statement: OpaquePointer?
let queryString = "SELECT * FROM members WHERE email = ?"
if sqlite3_prepare_v2(database, queryString, -1, &statement, nil) == SQLITE_OK {
sqlite3_bind_text(statement, 1, inputValue, -1, nil)
while sqlite3_step(statement) == SQLITE_ROW {
// 결과 처리
}
}
sqlite3_finalize(statement)
sqlite3_close(database)
}
민감 데이터 암호화 저장
키체인(Keychain) 서비스를 활용하여 사용자의 민감 정보를 안전하게 저장할 수 있습니다.
import Security
func storeEncryptedData(identifier: String, content: Data) -> Bool {
let attributes: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: identifier,
kSecValueData as String: content
]
SecItemDelete(attributes as CFDictionary)
return SecItemAdd(attributes as CFDictionary, nil) == errSecSuccess
}
func retrieveEncryptedData(identifier: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: identifier,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
return status == errSecSuccess ? result as? Data : nil
}
입력값 유효성 검사 및 정제
사용자로부터 받은 입력값은 반드시 검증 및 정제 과정을 거쳐 XSS(Cross-Site Scripting)와 같은 공격을 방지해야 합니다.
func validateAndClean(input: String) -> String {
var cleaned = input
let dangerousPatterns = ["<script>", "</script>", "javascript:", "onload=", "onclick="]
for pattern in dangerousPatterns {
cleaned = cleaned.replacingOccurrences(of: pattern, with: "", options: [.caseInsensitive])
}
return cleaned
}
// 사용 예제
let rawInput = "<script>alert('보안 위협')</script>"
let safeInput = validateAndClean(input: rawInput)
print(safeInput) // 출력: alert('보안 위협')
Objective-C 버전 구현
HTTPS 설정
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
SSL 인증서 검증
#import <Foundation/Foundation.h>
@interface SecureConnectionDelegate : NSObject <NSURLSessionDelegate>
@end
@implementation SecureConnectionDelegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, 0);
NSData *serverCertData = CFBridgingRelease(SecCertificateCopyData(cert));
NSString *certPath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"der"];
NSData *bundledCertData = [NSData dataWithContentsOfFile:certPath];
if ([serverCertData isEqualToData:bundledCertData]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
handler(NSURLSessionAuthChallengeUseCredential, credential);
return;
}
}
handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
@end
파라미터화된 SQL 쿼리
#import <sqlite3.h>
- (void)performSecureDatabaseQuery:(NSString *)userEmail {
sqlite3 *dbHandle;
if (sqlite3_open([databasePath UTF8String], &dbHandle) == SQLITE_OK) {
sqlite3_stmt *preparedStmt;
const char *sqlQuery = "SELECT * FROM members WHERE email = ?";
if (sqlite3_prepare_v2(dbHandle, sqlQuery, -1, &preparedStmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(preparedStmt, 1, [userEmail UTF8String], -1, SQLITE_TRANSIENT);
while (sqlite3_step(preparedStmt) == SQLITE_ROW) {
// 데이터 처리
}
}
sqlite3_finalize(preparedStmt);
sqlite3_close(dbHandle);
}
}
키체인 데이터 관리
#import <Security/Security.h>
- (BOOL)saveSensitiveData:(NSData *)data forKey:(NSString *)key {
NSDictionary *attributes = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecValueData: data};
SecItemDelete((__bridge CFDictionaryRef)attributes);
return SecItemAdd((__bridge CFDictionaryRef)attributes, NULL) == errSecSuccess;
}
- (NSData *)loadSensitiveDataForKey:(NSString *)key {
NSDictionary *searchQuery = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne};
CFTypeRef resultRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, &resultRef);
return status == noErr ? CFBridgingRelease(resultRef) : nil;
}
입력값 보안 처리
- (NSString *)sanitizeUserContent:(NSString *)content {
NSArray *patterns = @[@"<script>", @"</script>", @"javascript:", @"onload=", @"onclick="];
NSMutableString *cleanedContent = [content mutableCopy];
for (NSString *pattern in patterns) {
NSRange range = [cleanedContent rangeOfString:pattern options:NSCaseInsensitiveSearch];
while (range.location != NSNotFound) {
[cleanedContent deleteCharactersInRange:range];
range = [cleanedContent rangeOfString:pattern options:NSCaseInsensitiveSearch];
}
}
return [cleanedContent copy];
}