파일 업로드
  1. base64hub
  2. 코드로 다루는 Base64
  3. Swift
코드

Swift에서 Base64 사용하기

Swift는 Base64에 Foundation의 Data를 씁니다. 인코딩은 메서드 하나, 디코딩은 실패 가능한 이니셜라이저 하나입니다. 아래 코드는 Apple 플랫폼과 같은 Foundation API를 쓰는 Linux의 Swift 6.1에서 실행했습니다.

문자열 인코딩과 디코딩

String.utf8이 UTF-8 바이트를 주고, Data가 이를 인코딩합니다.

import Foundation

let enc = Data("héllo wörld".utf8).base64EncodedString()
print(enc)   // aMOpbGxvIHfDtnJsZA==

if let data = Data(base64Encoded: enc),
   let s = String(data: data, encoding: .utf8) {
    print(s) // héllo wörld
}

디코딩 경로에는 옵셔널이 두 개 있습니다. Data(base64Encoded:)는 잘못된 Base64에 대해 nil이고, String(data:encoding:)은 바이트가 올바른 UTF-8이 아닐 때(예: [0xFF, 0xFE]) nil입니다. nil보다 대체 문자가 낫다면 String(decoding: data, as: UTF8.self)를 사용하세요.

URL-safe Base64

Foundation은 URL-safe 알파벳을 모르므로 문자를 직접 변환하세요.

let d = Data("<<???>>".utf8)
let b64url = d.base64EncodedString()
    .replacingOccurrences(of: "+", with: "-")
    .replacingOccurrences(of: "/", with: "_")
    .replacingOccurrences(of: "=", with: "")
print(b64url)   // PDw_Pz8-Pg

func base64URLDecode(_ s: String) -> Data? {
    var b = s.replacingOccurrences(of: "-", with: "+")
             .replacingOccurrences(of: "_", with: "/")
    b += String(repeating: "=", count: (4 - b.count % 4) % 4)
    return Data(base64Encoded: b)
}

print(String(decoding: base64URLDecode("PDw_Pz8-Pg")!, as: UTF8.self))  // <<???>>

패딩 단계는 필수입니다. Data(base64Encoded: "aGk")는 nil을 반환합니다. JWT, Sign in with Apple의 ID 토큰, 많은 푸시 알림 페이로드가 이 패딩 없는 형식을 씁니다. 자세한 내용은 Base64와 Base64URL 비교와 패딩 설명을 참고하거나, JWT 디코더로 토큰을 살펴보세요.

검증: 디코딩이 nil을 반환할 때

잘못된 입력은 모두 오류 메시지 없이 같은 결과, 즉 nil을 냅니다. 다음은 모두 nil을 반환했습니다.

입력문제
PDw_Pz8-Pg==URL-safe 문자
aGk패딩 누락
aGk=!!패딩 뒤의 쓰레기 문자
aGk==패딩이 너무 많음
a불가능한 길이
"aGVs\nbG8K"줄 바꿈
" aGk="앞쪽 공백

통과하는 경우가 하나 있습니다. aGl=(0이 아닌 패딩 비트)는 hi로 디코딩됩니다. 따라서 유효성 검사는 Data(base64Encoded: s) != nil이면 충분합니다. 문자열이 왜 실패하는지 알아야 한다면 Base64 검사기가 위치와 이유를 알려 주고, 오류 가이드에서 각 오류를 설명합니다.

줄 바꿈이나 엉뚱한 문자가 섞인 입력에는 알파벳 밖의 문자를 모두 건너뛰는 .ignoreUnknownCharacters 옵션을 쓰세요.

Data(base64Encoded: "aGVs\nbG8K", options: .ignoreUnknownCharacters)
// 6 bytes: "hello\n"

신뢰하는 MIME이나 PEM 본문에만 쓰세요. 임의의 입력에 쓰면 URL-safe 문자까지 건너뛰어 결과가 조용히 틀려지므로 오류가 가려집니다.

줄 바꿈 옵션

base64EncodedString(options:)은 64자나 76자에서 줄을 바꿀 수 있습니다. 줄 끝 기본값은 CRLF입니다.

let x = Data(String(repeating: "x", count: 60).utf8)

x.base64EncodedString(options: .lineLength76Characters)
// "eHh4eHh4...eHh4\r\neHh4"    (76 chars, CRLF, rest)

x.base64EncodedString(options: .lineLength64Characters)
// 64 chars, "\r\n", 16 chars   (PEM-style)

Unix 줄 끝을 원하면 길이 옵션에 .endLineWithLineFeed만 조합하세요. [.lineLength76Characters, .endLineWithLineFeed]. 옵션이 없으면 출력은 한 줄이며, JSON, URL, 헤더에 필요한 형태가 바로 이것입니다. 이메일용 76자 배치는 MIME Base64 인코더에서 볼 수 있습니다.

파일 인코딩과 디코딩

let url = URL(fileURLWithPath: "hello.txt")
let b64 = try Data(contentsOf: url).base64EncodedString()
print(b64)   // aGVsbG8K

try Data(base64Encoded: "aGVsbG8K")!.write(to: URL(fileURLWithPath: "copy.txt"))

플레이그라운드에서는 강제 언래핑도 괜찮지만, 앱에서는 디코딩한 Data를 guard let으로 받고 오류를 보고하세요.

큰 파일을 조각으로 처리하기

Foundation에는 스트리밍 Base64 인코더가 없습니다. 중간에 패딩이 생기지 않도록 크기가 3의 배수인 조각으로 파일을 읽으세요.

func encodeFile(from src: URL, to dst: URL) throws {
    _ = FileManager.default.createFile(atPath: dst.path, contents: nil)
    let input = try FileHandle(forReadingFrom: src)
    let output = try FileHandle(forWritingTo: dst)
    defer { try? input.close(); try? output.close() }

    while let chunk = try input.read(upToCount: 3 * 64 * 1024), !chunk.isEmpty {
        try output.write(contentsOf: Data(chunk.base64EncodedString().utf8))
    }
}

이 방식으로 300 KB 테스트 파일을 인코딩하면 한 번의 호출로 인코딩한 것과 같은 문자열이 나왔습니다. read(upToCount:)와 write(contentsOf:)는 macOS 10.15.4 / iOS 13.4 이상이 필요합니다. 출력이 입력의 약 4/3이 되는 이유는 크기 증가 가이드에서 설명합니다.

함정

  • 오류 메시지를 기대하기. Data(base64Encoded:)는 “안 된다"고만 말합니다. 디버깅하려면 입력 길이와 앞 몇 글자를 로그로 남기세요.
  • Data URI 접두사. 웹 뷰에서 온 문자열은 흔히 data:image/png;base64,로 시작합니다. 디코딩 전에 첫 번째 쉼표에서 잘라 내지 않으면 결과가 nil입니다.
  • String(describing: data). Data를 출력하면 내용이 아니라 6 bytes 같은 것이 보입니다. base64EncodedString()이나 String(decoding:as:)를 사용하세요.
  • Base64를 안전한 저장소로 취급하기. 비밀 정보에는 키체인을 쓰세요. Base64는 누구나 읽을 수 있습니다. 인코딩과 암호화의 차이를 참고하세요.

자주 묻는 질문

Data(base64Encoded:)가 nil을 반환하는 이유는 무엇인가요?

이 이니셜라이저는 잘못된 입력이면 예외를 던지는 대신 nil을 반환합니다. 패딩 누락, URL-safe 문자 -나 _, 공백, 줄 바꿈, data: 접두사가 원인입니다. 먼저 입력을 고치거나, 줄 바꿈된 입력이라면 options: .ignoreUnknownCharacters를 넘기세요.

Swift는 Base64URL을 기본으로 지원하나요?

Foundation에는 URL-safe 옵션이 없습니다. 인코딩한 뒤 +를 -로, /를 _로 바꾸고 =를 제거하세요. 디코딩 전에는 이를 반대로 하고 패딩을 복원하세요.

Swift에서 UIImage를 Base64로 변환하려면 어떻게 하나요?

먼저 image.pngData()나 image.jpegData(compressionQuality: 0.8) 등으로 바이트를 얻은 다음, 그 결과에 .base64EncodedString()을 호출하세요. 출력을 확인하려면 Base64 이미지 변환에 붙여넣으세요.

계속 읽기