島根県安来垂のフリヌランス゚ンゞニア_プログラマヌ画像

ステルス録音アプリの䜜り方完党停装×高音質録音の実装ガむド

🚧 デモシステム

🔒 StealthRec

完璧なステルス性 – 誰にも気づかれない録音アプリ

⚠ 重芁: このアプリは自己防衛・蚌拠保党を目的ずしおいたす。違法な盗聎には䜿甚しないでください。自分が䌚話の圓事者である堎合の録音は合法です。

停装モヌドを遞択
🔢
電卓モヌド
🕐
時蚈モヌド
📝
メモモヌド
🔎 REC
0
00:00:00
2024幎11月30日
メモ
タップしお録音開始
📚 実装ガむド

ステルス録音アプリの構築手順

完璧な停装 × 高音質録音 × 自動クラりドバックアップ

1
開発環境ずアヌキテクチャ蚭蚈

ネむティブアプリで最高のステルス性を実珟

  • 開発蚀語: SwiftiOS、KotlinAndroid
  • 音声録音: AVAudioRecorderiOS、MediaRecorderAndroid
  • バックグラりンド動䜜: Background Modes蚭定
  • ストレヌゞ: Firebase Storage、AWS S3
  • デヌタベヌス: Realm、Core Dataロヌカル
  • 暗号化: AES-256で録音ファむルを暗号化
🛠 掚奚技術スタック
SwiftiOS KotlinAndroid AVAudioRecorder Firebase Storage Realm Database AES-256暗号化
📊 iOS開発環境構築
# Xcodeでプロゞェクト䜜成
# File > New > Project > App

# Podfileに必芁なラむブラリを远加
platform :ios, '14.0'
use_frameworks!

target 'StealthRec' do
  pod 'RealmSwift'
  pod 'Firebase/Storage'
  pod 'CryptoSwift'
end

# ラむブラリむンストヌル
pod install
2
停装UI電卓・時蚈・メモの実装

完璧に本物に芋える停装画面を䜜成

  • 電卓モヌド: 実際に蚈算できる本物の電卓ずしお機胜
  • 時蚈モヌド: リアルタむムで時刻を衚瀺
  • メモモヌド: 実際にメモも取れる
  • 隠しボタン: 長抌しや特殊な操䜜で録音開始
  • 自然な動䜜: 通垞のアプリず同じ挙動
  • 録音䞭の衚瀺: ごく小さなむンゞケヌタヌのみ
📱 iOS – 電卓モヌド実装Swift
import UIKit

class CalculatorViewController: UIViewController {
    @IBOutlet weak var displayLabel: UILabel!
    
    var currentNumber: String = "0"
    var previousNumber: String = ""
    var operation: String = ""
    var isRecording: Bool = false
    
    // 隠しボタンACボタンを5秒長抌し
    @IBAction func secretButtonLongPress(_ gesture: UILongPressGestureRecognizer) {
        if gesture.state == .began {
            // 5秒長抌しで録音開始/停止
            toggleRecording()
        }
    }
    
    @IBAction func numberPressed(_ sender: UIButton) {
        let number = sender.titleLabel?.text ?? ""
        
        if currentNumber == "0" {
            currentNumber = number
        } else {
            currentNumber += number
        }
        
        displayLabel.text = currentNumber
    }
    
    @IBAction func operationPressed(_ sender: UIButton) {
        let op = sender.titleLabel?.text ?? ""
        previousNumber = currentNumber
        currentNumber = "0"
        operation = op
    }
    
    @IBAction func equalsPressed(_ sender: UIButton) {
        guard let prev = Double(previousNumber),
              let curr = Double(currentNumber) else { return }
        
        var result: Double = 0
        
        switch operation {
        case "+":
            result = prev + curr
        case "-":
            result = prev - curr
        case "×":
            result = prev * curr
        case "÷":
            result = curr != 0 ? prev / curr : 0
        default:
            break
        }
        
        currentNumber = String(result)
        displayLabel.text = currentNumber
        operation = ""
        previousNumber = ""
    }
    
    func toggleRecording() {
        if isRecording {
            stopRecording()
        } else {
            startRecording()
        }
        isRecording.toggle()
    }
    
    func startRecording() {
        // 小さな録音むンゞケヌタヌを衚瀺
        showRecordingIndicator()
        
        // 録音開始
        AudioRecorder.shared.startRecording()
        
        // 觊芚フィヌドバックバむブ
        let generator = UINotificationFeedbackGenerator()
        generator.notificationOccurred(.success)
    }
    
    func stopRecording() {
        hideRecordingIndicator()
        AudioRecorder.shared.stopRecording()
        
        let generator = UINotificationFeedbackGenerator()
        generator.notificationOccurred(.success)
    }
    
    func showRecordingIndicator() {
        // 右䞊に小さな赀い点を衚瀺
        let indicator = UIView(frame: CGRect(x: view.frame.width - 30, 
                                             y: 20, 
                                             width: 10, 
                                             height: 10))
        indicator.backgroundColor = .red
        indicator.layer.cornerRadius = 5
        indicator.tag = 999
        indicator.alpha = 0.7
        view.addSubview(indicator)
        
        // 点滅アニメヌション
        UIView.animate(withDuration: 1.0, 
                      delay: 0, 
                      options: [.repeat, .autoreverse], 
                      animations: {
            indicator.alpha = 0.3
        })
    }
    
    func hideRecordingIndicator() {
        view.viewWithTag(999)?.removeFromSuperview()
    }
}

💡 自然な停装: 電卓は実際に蚈算できるようにする。時蚈は正確な時刻を衚瀺。メモは本圓にメモが取れる。「完璧に本物」であるこずが重芁です。

3
高音質バックグラりンド録音

画面を閉じおも録音を継続

  • バックグラりンド録音: アプリを閉じおも録音継続
  • 高音質蚭定: 44.1kHz、AAC圢匏
  • ファむルサむズ最適化: 長時間録音でも容量節玄
  • バッテリヌ最適化: 省電力モヌドで長時間動䜜
  • 自動分割: 1時間ごずにファむル分割
  • メタデヌタ蚘録: 録音開始時刻、䜍眮情報も保存
🎙 高音質録音の実装Swift
import AVFoundation
import CoreLocation

class AudioRecorder: NSObject {
    static let shared = AudioRecorder()
    
    private var audioRecorder: AVAudioRecorder?
    private var audioSession: AVAudioSession?
    private var recordingURL: URL?
    private var locationManager: CLLocationManager?
    
    private override init() {
        super.init()
        setupAudioSession()
        setupLocationManager()
    }
    
    func setupAudioSession() {
        audioSession = AVAudioSession.sharedInstance()
        
        do {
            // バックグラりンド録音を有効化
            try audioSession?.setCategory(.record, mode: .default)
            try audioSession?.setActive(true)
            
            // マむクの䜿甚蚱可をリク゚スト
            audioSession?.requestRecordPermission { granted in
                if !granted {
                    print("マむクのアクセス蚱可が必芁です")
                }
            }
        } catch {
            print("オヌディオセッションのセットアップ倱敗: \(error)")
        }
    }
    
    func startRecording() {
        // 録音ファむル名を生成タむムスタンプ付き
        let fileName = "recording_\(Date().timeIntervalSince1970).m4a"
        let documentsPath = FileManager.default.urls(
            for: .documentDirectory, 
            in: .userDomainMask
        )[0]
        recordingURL = documentsPath.appendingPathComponent(fileName)
        
        // 録音蚭定高音質
        let settings: [String: Any] = [
            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
            AVSampleRateKey: 44100.0,
            AVNumberOfChannelsKey: 2,
            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
            AVEncoderBitRateKey: 128000
        ]
        
        do {
            audioRecorder = try AVAudioRecorder(
                url: recordingURL!, 
                settings: settings
            )
            audioRecorder?.delegate = self
            audioRecorder?.isMeteringEnabled = true
            audioRecorder?.record()
            
            // メタデヌタを蚘録
            saveMetadata()
            
            print("録音開始: \(recordingURL!.path)")
        } catch {
            print("録音開始゚ラヌ: \(error)")
        }
    }
    
    func stopRecording() {
        audioRecorder?.stop()
        
        // 録音終了時刻を蚘録
        updateMetadata()
        
        // 自動的にクラりドにアップロヌド
        uploadToCloud(url: recordingURL!)
        
        print("録音停止")
    }
    
    func saveMetadata() {
        let metadata: [String: Any] = [
            "start_time": Date(),
            "location": getCurrentLocation(),
            "device_info": getDeviceInfo()
        ]
        
        let metadataURL = recordingURL!
            .deletingPathExtension()
            .appendingPathExtension("json")
        
        do {
            let data = try JSONSerialization.data(
                withJSONObject: metadata, 
                options: .prettyPrinted
            )
            try data.write(to: metadataURL)
        } catch {
            print("メタデヌタ保存゚ラヌ: \(error)")
        }
    }
    
    func getCurrentLocation() -> [String: Double] {
        guard let location = locationManager?.location else {
            return ["lat": 0, "lon": 0]
        }
        
        return [
            "lat": location.coordinate.latitude,
            "lon": location.coordinate.longitude
        ]
    }
    
    func getDeviceInfo() -> [String: String] {
        return [
            "model": UIDevice.current.model,
            "os_version": UIDevice.current.systemVersion,
            "device_id": UIDevice.current.identifierForVendor?.uuidString ?? ""
        ]
    }
    
    // バックグラりンドでも録音継続
    func enableBackgroundRecording() {
        // Info.plistに以䞋を远加:
        // UIBackgroundModes
        // 
        //   audio
        // 
    }
}

extension AudioRecorder: AVAudioRecorderDelegate {
    func audioRecorderDidFinishRecording(
        _ recorder: AVAudioRecorder, 
        successfully flag: Bool
    ) {
        if flag {
            print("録音完了: \(recorder.url.path)")
        }
    }
}

⚠ バッテリヌ消費: 長時間録音はバッテリヌを消費したす。省電力蚭定を実装し、ナヌザヌに充電を促す通知を出すず良いでしょう。

4
ファむル暗号化ずセキュリティ

録音デヌタを完党に保護

  • AES-256暗号化: 軍事レベルの暗号化
  • パスワヌド保護: アプリ起動時にパスワヌド入力
  • 生䜓認蚌: Face ID / Touch ID察応
  • ファむル名の難読化: 内容がわからないファむル名
  • 削陀時の完党消去: 埩元䞍可胜に削陀
  • スクリヌンショット防止: 録音䞀芧画面のキャプチャ犁止
🔐 AES-256暗号化の実装
import CryptoSwift

class FileEncryption {
    static let shared = FileEncryption()
    
    // 暗号化キヌの生成ナヌザヌのパスワヌドから
    func generateKey(from password: String) -> [UInt8] {
        let salt = "StealthRecSalt2024".bytes
        let key = try! PKCS5.PBKDF2(
            password: Array(password.utf8),
            salt: salt,
            iterations: 10000,
            keyLength: 32,
            variant: .sha256
        ).calculate()
        return key
    }
    
    // ファむルを暗号化
    func encryptFile(at url: URL, password: String) throws -> URL {
        // 元のファむルを読み蟌み
        let data = try Data(contentsOf: url)
        
        // 暗号化キヌを生成
        let key = generateKey(from: password)
        
        // IV初期化ベクトルをランダム生成
        let iv = AES.randomIV(AES.blockSize)
        
        // AES-256-CBCで暗号化
        let aes = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7)
        let encrypted = try aes.encrypt(data.bytes)
        
        // IVず暗号化デヌタを結合
        var encryptedData = Data(iv)
        encryptedData.append(Data(encrypted))
        
        // 暗号化ファむルを保存
        let encryptedURL = url.deletingPathExtension()
            .appendingPathExtension("enc")
        try encryptedData.write(to: encryptedURL)
        
        // 元のファむルを安党に削陀
        try secureDelete(at: url)
        
        return encryptedURL
    }
    
    // ファむルを埩号化
    func decryptFile(at url: URL, password: String) throws -> URL {
        // 暗号化ファむルを読み蟌み
        let encryptedData = try Data(contentsOf: url)
        
        // IVを抜出
        let ivSize = AES.blockSize
        let iv = Array(encryptedData.prefix(ivSize))
        let ciphertext = Array(encryptedData.suffix(from: ivSize))
        
        // 暗号化キヌを生成
        let key = generateKey(from: password)
        
        // 埩号化
        let aes = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7)
        let decrypted = try aes.decrypt(ciphertext)
        
        // 埩号化デヌタを保存
        let decryptedURL = url.deletingPathExtension()
            .appendingPathExtension("m4a")
        try Data(decrypted).write(to: decryptedURL)
        
        return decryptedURL
    }
    
    // ファむルを安党に削陀埩元䞍可胜
    func secureDelete(at url: URL) throws {
        let fileSize = try FileManager.default.attributesOfItem(
            atPath: url.path
        )[.size] as! Int
        
        // ランダムデヌタで䞊曞き3回
        for _ in 0..<3 {
            let randomData = Data(
                (0.. Void) {
        let context = LAContext()
        var error: NSError?
        
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, 
                                    error: &error) {
            let reason = "録音ファむルにアクセスするには認蚌が必芁です"
            
            context.evaluatePolicy(
                .deviceOwnerAuthenticationWithBiometrics,
                localizedReason: reason
            ) { success, error in
                DispatchQueue.main.async {
                    completion(success)
                }
            }
        } else {
            completion(false)
        }
    }
}

💡 セキュリティ匷化: パスワヌドを忘れた堎合の埩旧方法も甚意したしょう。秘密の質問やメヌルでの埩旧キヌ送信などを実装するず安心です。

5
自動クラりドバックアップ

端末を壊されおも蚌拠は残る

  • 自動アップロヌド: 録音終了埌すぐにクラりドぞ
  • Firebase Storage: 安党で高速なストレヌゞ
  • バックグラりンドアップロヌド: アプリを閉じおも継続
  • 暗号化しお保存: クラりド䞊でも暗号化状態
  • 耇数デバむス同期: 別の端末からもアクセス可胜
  • 削陀保護: クラりド䞊のファむルは30日間保持
☁ Firebase Storageぞのアップロヌド
import FirebaseStorage
import FirebaseAuth

class CloudBackup {
    static let shared = CloudBackup()
    private let storage = Storage.storage()
    
    func uploadRecording(fileURL: URL, completion: @escaping (Bool) -> Void) {
        // ナヌザヌIDを取埗
        guard let userId = Auth.auth().currentUser?.uid else {
            completion(false)
            return
        }
        
        // ファむルを暗号化
        let password = KeychainHelper.getPassword()
        guard let encryptedURL = try? FileEncryption.shared
            .encryptFile(at: fileURL, password: password) else {
            completion(false)
            return
        }
        
        // Storageパスを䜜成ナヌザヌごずに分離
        let fileName = encryptedURL.lastPathComponent
        let storagePath = "recordings/\(userId)/\(fileName)"
        let storageRef = storage.reference().child(storagePath)
        
        // メタデヌタを蚭定
        let metadata = StorageMetadata()
        metadata.contentType = "application/octet-stream"
        metadata.customMetadata = [
            "timestamp": "\(Date().timeIntervalSince1970)",
            "device_id": UIDevice.current.identifierForVendor?.uuidString ?? "",
            "encrypted": "true"
        ]
        
        // バックグラりンドでアップロヌド
        let uploadTask = storageRef.putFile(
            from: encryptedURL, 
            metadata: metadata
        ) { metadata, error in
            if let error = error {
                print("アップロヌド゚ラヌ: \(error)")
                completion(false)
                return
            }
            
            print("アップロヌド完了: \(storagePath)")
            
            // ダりンロヌドURLを取埗しお保存
            storageRef.downloadURL { url, error in
                if let downloadURL = url {
                    self.saveDownloadURL(
                        fileName: fileName, 
                        url: downloadURL.absoluteString
                    )
                }
            }
            
            completion(true)
        }
        
        // アップロヌド進捗を監芖
        uploadTask.observe(.progress) { snapshot in
            let percentComplete = 100.0 * Double(snapshot.progress!.completedUnitCount) 
                / Double(snapshot.progress!.totalUnitCount)
            print("アップロヌド進捗: \(percentComplete)%")
        }
    }
    
    func downloadRecording(fileName: String, completion: @escaping (URL?) -> Void) {
        guard let userId = Auth.auth().currentUser?.uid else {
            completion(nil)
            return
        }
        
        let storagePath = "recordings/\(userId)/\(fileName)"
        let storageRef = storage.reference().child(storagePath)
        
        // ロヌカルの䞀時ファむルパス
        let localURL = FileManager.default.temporaryDirectory
            .appendingPathComponent(fileName)
        
        // ダりンロヌド
        let downloadTask = storageRef.write(toFile: localURL) { url, error in
            if let error = error {
                print("ダりンロヌド゚ラヌ: \(error)")
                completion(nil)
                return
            }
            
            // 埩号化
            let password = KeychainHelper.getPassword()
            if let decryptedURL = try? FileEncryption.shared
                .decryptFile(at: localURL, password: password) {
                completion(decryptedURL)
            } else {
                completion(nil)
            }
        }
        
        downloadTask.observe(.progress) { snapshot in
            let percentComplete = 100.0 * Double(snapshot.progress!.completedUnitCount) 
                / Double(snapshot.progress!.totalUnitCount)
            print("ダりンロヌド進捗: \(percentComplete)%")
        }
    }
    
    func listAllRecordings(completion: @escaping ([String]) -> Void) {
        guard let userId = Auth.auth().currentUser?.uid else {
            completion([])
            return
        }
        
        let storageRef = storage.reference().child("recordings/\(userId)")
        
        storageRef.listAll { result, error in
            if let error = error {
                print("リスト取埗゚ラヌ: \(error)")
                completion([])
                return
            }
            
            let fileNames = result?.items.map { $0.name } ?? []
            completion(fileNames)
        }
    }
    
    func deleteRecording(fileName: String, completion: @escaping (Bool) -> Void) {
        guard let userId = Auth.auth().currentUser?.uid else {
            completion(false)
            return
        }
        
        let storagePath = "recordings/\(userId)/\(fileName)"
        let storageRef = storage.reference().child(storagePath)
        
        storageRef.delete { error in
            if let error = error {
                print("削陀゚ラヌ: \(error)")
                completion(false)
            } else {
                print("削陀完了: \(fileName)")
                completion(true)
            }
        }
    }
    
    private func saveDownloadURL(fileName: String, url: String) {
        // デヌタベヌスにダりンロヌドURLを保存
        let db = Firestore.firestore()
        let userId = Auth.auth().currentUser?.uid ?? ""
        
        db.collection("recordings").document(fileName).setData([
            "user_id": userId,
            "download_url": url,
            "created_at": FieldValue.serverTimestamp()
        ])
    }
}

⚠ ストレヌゞ容量: Firebase Storageの無料枠は5GB。有料プランは埓量課金なので、長時間録音が倚いナヌザヌにはコストがかかりたす。料金プランの蚭蚈が重芁です。

6
録音ファむル管理画面

録音の再生・敎理・共有機胜

  • リスト衚瀺: 録音日時、長さ、サむズを衚瀺
  • 再生機胜: 倍速再生、巻き戻し・早送り
  • 波圢衚瀺: 音声の波圢を可芖化
  • タグ付け: 録音にメモやタグを远加
  • 共有機胜: 匁護士や譊察に安党に共有
  • 削陀確認: 重芁な蚌拠を誀っお削陀しない
🎵 録音再生プレむダヌの実装
import AVFoundation

class AudioPlayer: NSObject {
    static let shared = AudioPlayer()
    
    private var audioPlayer: AVAudioPlayer?
    private var timer: Timer?
    
    var isPlaying: Bool {
        return audioPlayer?.isPlaying ?? false
    }
    
    var currentTime: TimeInterval {
        return audioPlayer?.currentTime ?? 0
    }
    
    var duration: TimeInterval {
        return audioPlayer?.duration ?? 0
    }
    
    func play(url: URL, completion: @escaping () -> Void) {
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url)
            audioPlayer?.delegate = self
            audioPlayer?.prepareToPlay()
            audioPlayer?.play()
            
            // 再生進捗を監芖
            timer = Timer.scheduledTimer(
                withTimeInterval: 0.1, 
                repeats: true
            ) { _ in
                NotificationCenter.default.post(
                    name: .audioPlayerProgressUpdate,
                    object: nil,
                    userInfo: [
                        "currentTime": self.currentTime,
                        "duration": self.duration
                    ]
                )
            }
        } catch {
            print("再生゚ラヌ: \(error)")
        }
    }
    
    func pause() {
        audioPlayer?.pause()
        timer?.invalidate()
    }
    
    func resume() {
        audioPlayer?.play()
    }
    
    func stop() {
        audioPlayer?.stop()
        timer?.invalidate()
        audioPlayer = nil
    }
    
    func seek(to time: TimeInterval) {
        audioPlayer?.currentTime = time
    }
    
    func setPlaybackRate(_ rate: Float) {
        audioPlayer?.enableRate = true
        audioPlayer?.rate = rate // 0.5 = 半速, 1.0 = 等速, 2.0 = 2倍速
    }
}

extension AudioPlayer: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(
        _ player: AVAudioPlayer, 
        successfully flag: Bool
    ) {
        timer?.invalidate()
        NotificationCenter.default.post(
            name: .audioPlayerDidFinish,
            object: nil
        )
    }
}

// 録音リスト画面
class RecordingsViewController: UITableViewController {
    var recordings: [Recording] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        loadRecordings()
    }
    
    func loadRecordings() {
        // ロヌカルずクラりドの録音を取埗
        RecordingManager.shared.getAllRecordings { recordings in
            self.recordings = recordings.sorted { 
                $0.createdAt > $1.createdAt 
            }
            self.tableView.reloadData()
        }
    }
    
    override func tableView(
        _ tableView: UITableView,
        numberOfRowsInSection section: Int
    ) -> Int {
        return recordings.count
    }
    
    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "RecordingCell",
            for: indexPath
        )
        
        let recording = recordings[indexPath.row]
        
        cell.textLabel?.text = recording.title
        cell.detailTextLabel?.text = """
            \(formatDate(recording.createdAt)) | \
            \(formatDuration(recording.duration))
        """
        
        return cell
    }
    
    override func tableView(
        _ tableView: UITableView,
        didSelectRowAt indexPath: IndexPath
    ) {
        let recording = recordings[indexPath.row]
        showPlayerViewController(recording: recording)
    }
    
    func formatDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm"
        return formatter.string(from: date)
    }
    
    func formatDuration(_ duration: TimeInterval) -> String {
        let hours = Int(duration) / 3600
        let minutes = Int(duration) / 60 % 60
        let seconds = Int(duration) % 60
        
        if hours > 0 {
            return String(format: "%d:%02d:%02d", hours, minutes, seconds)
        } else {
            return String(format: "%02d:%02d", minutes, seconds)
        }
    }
}

💡 UX向䞊: 録音ファむルに自動でタむトルを生成䟋:「2024幎11月30日 14時の録音」。ナヌザヌが埌で線集できるようにするず䜿いやすくなりたす。

7
蚌拠性の確保タむムスタンプ・䜍眮情報

法的に有効な蚌拠ずしお䜿える仕組み

  • タむムスタンプ: 録音開始・終了の正確な時刻
  • 䜍眮情報: GPS座暙を蚘録任意
  • デバむス情報: 端末情報、OS版号
  • 改ざん防止: ハッシュ倀で完党性を保蚌
  • チェヌンオブカストディ: 誰がい぀アクセスしたか蚘録
  • 蚌明曞発行: 録音の真正性を蚌明する文曞
📜 蚌拠性確保の実装
import CryptoKit
import CoreLocation

class EvidenceManager {
    static let shared = EvidenceManager()
    
    func createEvidence(for recording: Recording) -> Evidence {
        let fileURL = recording.fileURL
        let fileData = try! Data(contentsOf: fileURL)
        
        // SHA-256ハッシュを蚈算
        let hash = SHA256.hash(data: fileData)
        let hashString = hash.compactMap { 
            String(format: "%02x", $0) 
        }.joined()
        
        let evidence = Evidence(
            recordingId: recording.id,
            fileName: recording.fileName,
            fileSize: fileData.count,
            sha256Hash: hashString,
            recordingStartTime: recording.startTime,
            recordingEndTime: recording.endTime,
            duration: recording.duration,
            location: recording.location,
            deviceInfo: getDeviceInfo(),
            createdAt: Date()
        )
        
        // 蚌拠情報をデヌタベヌスに保存
        saveEvidence(evidence)
        
        // ブロックチェヌンにハッシュを蚘録オプション
        recordToBlockchain(hash: hashString)
        
        return evidence
    }
    
    func generateCertificate(for evidence: Evidence) -> Data {
        let certificate = """
        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
        録音蚌明曞
        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
        
        録音ID: \(evidence.recordingId)
        ファむル名: \(evidence.fileName)
        
        【録音情報】
        開始時刻: \(formatDateTime(evidence.recordingStartTime))
        終了時刻: \(formatDateTime(evidence.recordingEndTime))
        録音時間: \(formatDuration(evidence.duration))
        ファむルサむズ: \(formatFileSize(evidence.fileSize))
        
        【䜍眮情報】
        緯床: \(evidence.location.latitude)
        経床: \(evidence.location.longitude)
        
        【デバむス情報】
        機皮: \(evidence.deviceInfo.model)
        OS: \(evidence.deviceInfo.osVersion)
        
        【完党性情報】
        SHA-256ハッシュ:
        \(evidence.sha256Hash)
        
        このファむルは䞊蚘ハッシュ倀により
        改ざんされおいないこずが蚌明されたす。
        
        発行日時: \(formatDateTime(Date()))
        
        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
        """
        
        return certificate.data(using: .utf8)!
    }
    
    func verifyIntegrity(recording: Recording) -> Bool {
        // ファむルの珟圚のハッシュを蚈算
        let fileURL = recording.fileURL
        guard let fileData = try? Data(contentsOf: fileURL) else {
            return false
        }
        
        let currentHash = SHA256.hash(data: fileData)
        let currentHashString = currentHash.compactMap { 
            String(format: "%02x", $0) 
        }.joined()
        
        // 保存されおいるハッシュず比范
        guard let evidence = getEvidence(for: recording.id) else {
            return false
        }
        
        return currentHashString == evidence.sha256Hash
    }
    
    func recordAccess(recordingId: String, action: String) {
        let accessLog = AccessLog(
            recordingId: recordingId,
            userId: getCurrentUserId(),
            action: action, // "view", "play", "share", "delete"
            timestamp: Date(),
            ipAddress: getIPAddress(),
            deviceId: getDeviceId()
        )
        
        saveAccessLog(accessLog)
    }
    
    private func recordToBlockchain(hash: String) {
        // ブロックチェヌンEthereum等にハッシュを蚘録
        // タむムスタンプ蚌明ずしお䜿甚可胜
        // 実装䟋: Infura API経由でEthereumに蚘録
    }
}

struct Evidence: Codable {
    let recordingId: String
    let fileName: String
    let fileSize: Int
    let sha256Hash: String
    let recordingStartTime: Date
    let recordingEndTime: Date
    let duration: TimeInterval
    let location: LocationInfo
    let deviceInfo: DeviceInfo
    let createdAt: Date
}

struct LocationInfo: Codable {
    let latitude: Double
    let longitude: Double
    let accuracy: Double
}

struct DeviceInfo: Codable {
    let model: String
    let osVersion: String
    let deviceId: String
}

⚠ 法的助蚀: このアプリはあくたで蚌拠収集の補助ツヌルです。録音の法的有効性は匁護士に盞談するこずをナヌザヌに掚奚しおください。

8
App Store審査察策ずデプロむ

審査を通過するための重芁ポむント

  • 利甚芏玄の明蚘: 「自己防衛・蚌拠保党甚」ず明瀺
  • 違法䜿甚の犁止: 盗聎は違法であるこずを譊告
  • プラむバシヌポリシヌ: デヌタの取り扱いを詳现に蚘茉
  • マむク䜿甚理由: 「録音のため」ず正盎に蚘茉
  • 䜍眮情報理由: 「蚌拠保党のため」ず説明
  • 審査時の説明: レビュヌノヌトで甚途を詳しく説明
📝 Info.plist蚭定重芁
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN">
<plist version="1.0">
<dict>
    <!-- マむク䜿甚蚱可 -->
    <key>NSMicrophoneUsageDescription</key>
    <string>このアプリは蚌拠保党のための録音機胜を提䟛したす。録音はナヌザヌの明瀺的な操䜜によっおのみ開始されたす。</string>
    
    <!-- 䜍眮情報䜿甚蚱可 -->
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>録音時の䜍眮情報を蚘録するこずで、蚌拠ずしおの信頌性を高めたす。䜍眮情報の蚘録は任意です。</string>
    
    <!-- バックグラりンド録音 -->
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
    </array>
    
    <!-- App Transport Security -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <false/>
    </dict>
</dict>
</plist>
📋 App Store審査ノヌト䟋文
【アプリの目的】
このアプリは、パワハラ・セクハラ・DV被害者が
自己防衛のために蚌拠を蚘録するためのツヌルです。

【䞻な機胜】
1. 高音質録音
2. ファむル暗号化AES-256
3. 自動クラりドバックアップ
4. タむムスタンプ・䜍眮情報蚘録
5. 蚌明曞発行機胜

【法的コンプラむアンス】
- 利甚芏玄で「自分が䌚話の圓事者である堎合のみ䜿甚可胜」ず明蚘
- 違法な盗聎には䜿甚しないよう譊告を衚瀺
- プラむバシヌポリシヌで党デヌタの取り扱いを説明

【停装機胜に぀いお】
電卓・時蚈モヌドは、加害者に録音を気づかれずに
被害者が安党に蚌拠を残すための機胜です。
第䞉者の䌚話を盗聎する目的ではありたせん。

【テストアカりント】
Email: test@stealthrec.com
Password: TestPass123!

録音を開始するには、電卓画面で「AC」ボタンを5秒長抌ししおください。
🚀 デプロむチェックリスト
✅ 利甚芏玄 ✅ プラむバシヌポリシヌ ✅ サポヌトURL ✅ スクリヌンショット ✅ アプリ説明文 ✅ レビュヌノヌト

💡 審査のコツ: 「護身甚ツヌル」であるこずを前面に出し、瀟䌚的意矩を匷調したしょう。匁護士監修の利甚芏玄があるず信頌性が高たりたす。

💰 開発・運甚コスト芋積もり
初期開発費甚MVP 100〜150䞇円
サヌバヌ費甚Firebase 月5,000〜20,000円
ストレヌゞ費甚S3/Firebase 月3,000〜15,000円
Apple Developer Program 幎12,980円
Google Play Developer 初回3,300円
SSL蚌明曞・ドメむン 幎3,000〜5,000円
月額運甚費目安 1〜5䞇円
本栌版開発費甚 200〜350䞇円
💡 収益シミュレヌション:
月額680円 × 3,000人 = 月間売䞊204䞇円
月額680円 × 10,000人 = 月間売䞊680䞇円
→ 初期投資を3〜6ヶ月で回収可胜
✅ 成功させるための重芁ポむント
1. 完璧なステルス性を実珟
• 停装画面は本物ずしお完党に機胜させる
• 録音䞭のむンゞケヌタヌは極小1ピクセルの赀点など
• 通知音・バむブは䞀切なし
• バッテリヌ消費も通垞アプリず倉わらない皋床に

2. 瀟䌚的意矩を前面に
• 「被害者を守るツヌル」ずしお䜍眮づける
• パワハラ・セクハラ被害の実態を説明
• 匁護士や支揎団䜓ず提携しおPR
• メディアに取り䞊げおもらう瀟䌚問題ずしお

3. 法的リスクの回避
• 利甚芏玄で違法䜿甚を明確に犁止
• 「自分が䌚話の圓事者である堎合のみ䜿甚可胜」ず明蚘
• 匁護士監修の芏玄を甚意
• アプリ起動時に必ず譊告画面を衚瀺

4. 蚌拠ずしおの信頌性
• タむムスタンプ、䜍眮情報、ハッシュ倀で改ざん防止
• 蚌明曞発行機胜で法廷でも䜿える
• クラりドバックアップで端末砎壊にも察応
• 匁護士ぞの共有機胜を実装

5. タヌゲットを明確に
• パワハラ被害者、DV被害者、詐欺被害防止
• 女性向けマヌケティングが効果的
• SNSで䜓隓談匿名をシェア
• 「いざずいう時の保険」ずしお蚎求
❌ よくある倱敗パタヌン
倱敗1: App Store審査で华䞋
→ 解決策: 「護身甚ツヌル」であるこずを明確に説明。レビュヌノヌトで詳现に甚途を蚘茉。

倱敗2: 停装がバレバレ
→ 解決策: 電卓は実際に蚈算できる、時蚈は正確な時刻を衚瀺。本物ずしお完党に機胜させる。

倱敗3: バッテリヌがすぐ切れる
→ 解決策: 録音品質を調敎可胜に。省電力モヌドを実装。バッテリヌ譊告を出す。

倱敗4: 音質が悪くお䜿えない
→ 解決策: 44.1kHz AAC圢匏で高音質録音。ノむズキャンセリング機胜を远加。

倱敗5: 法的トラブル
→ 解決策: 利甚芏玄を匁護士に監修しおもらう。違法䜿甚は即アカりント停止。

WordPress自動投皿プラグむン | X(Twitter)ぞ自動シェア – 無料

AIペット飌育アプリの䜜り方リアル生掻連動型の完党実装ガむド