🔒 StealthRec
完璧なステルス性 – 誰にも気づかれない録音アプリ
⚠️ 重要: このアプリは自己防衛・証拠保全を目的としています。違法な盗聴には使用しないでください。自分が会話の当事者である場合の録音は合法です。
ステルス録音アプリの構築手順
完璧な偽装 × 高音質録音 × 自動クラウドバックアップ
ネイティブアプリで最高のステルス性を実現
- 開発言語: Swift(iOS)、Kotlin(Android)
- 音声録音: AVAudioRecorder(iOS)、MediaRecorder(Android)
- バックグラウンド動作: Background Modes設定
- ストレージ: Firebase Storage、AWS S3
- データベース: Realm、Core Data(ローカル)
- 暗号化: AES-256で録音ファイルを暗号化
# 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
完璧に本物に見える偽装画面を作成
- 電卓モード: 実際に計算できる本物の電卓として機能
- 時計モード: リアルタイムで時刻を表示
- メモモード: 実際にメモも取れる
- 隠しボタン: 長押しや特殊な操作で録音開始
- 自然な動作: 通常のアプリと同じ挙動
- 録音中の表示: ごく小さなインジケーターのみ
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()
}
}
💡 自然な偽装: 電卓は実際に計算できるようにする。時計は正確な時刻を表示。メモは本当にメモが取れる。「完璧に本物」であることが重要です。
画面を閉じても録音を継続
- バックグラウンド録音: アプリを閉じても録音継続
- 高音質設定: 44.1kHz、AAC形式
- ファイルサイズ最適化: 長時間録音でも容量節約
- バッテリー最適化: 省電力モードで長時間動作
- 自動分割: 1時間ごとにファイル分割
- メタデータ記録: 録音開始時刻、位置情報も保存
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)")
}
}
}
⚠️ バッテリー消費: 長時間録音はバッテリーを消費します。省電力設定を実装し、ユーザーに充電を促す通知を出すと良いでしょう。
録音データを完全に保護
- AES-256暗号化: 軍事レベルの暗号化
- パスワード保護: アプリ起動時にパスワード入力
- 生体認証: Face ID / Touch ID対応
- ファイル名の難読化: 内容がわからないファイル名
- 削除時の完全消去: 復元不可能に削除
- スクリーンショット防止: 録音一覧画面のキャプチャ禁止
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)
}
}
}
💡 セキュリティ強化: パスワードを忘れた場合の復旧方法も用意しましょう。秘密の質問やメールでの復旧キー送信などを実装すると安心です。
端末を壊されても証拠は残る
- 自動アップロード: 録音終了後すぐにクラウドへ
- Firebase Storage: 安全で高速なストレージ
- バックグラウンドアップロード: アプリを閉じても継続
- 暗号化して保存: クラウド上でも暗号化状態
- 複数デバイス同期: 別の端末からもアクセス可能
- 削除保護: クラウド上のファイルは30日間保持
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。有料プランは従量課金なので、長時間録音が多いユーザーにはコストがかかります。料金プランの設計が重要です。
録音の再生・整理・共有機能
- リスト表示: 録音日時、長さ、サイズを表示
- 再生機能: 倍速再生、巻き戻し・早送り
- 波形表示: 音声の波形を可視化
- タグ付け: 録音にメモやタグを追加
- 共有機能: 弁護士や警察に安全に共有
- 削除確認: 重要な証拠を誤って削除しない
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時の録音」)。ユーザーが後で編集できるようにすると使いやすくなります。
法的に有効な証拠として使える仕組み
- タイムスタンプ: 録音開始・終了の正確な時刻
- 位置情報: 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
}
⚠️ 法的助言: このアプリはあくまで証拠収集の補助ツールです。録音の法的有効性は弁護士に相談することをユーザーに推奨してください。
審査を通過するための重要ポイント
- 利用規約の明記: 「自己防衛・証拠保全用」と明示
- 違法使用の禁止: 盗聴は違法であることを警告
- プライバシーポリシー: データの取り扱いを詳細に記載
- マイク使用理由: 「録音のため」と正直に記載
- 位置情報理由: 「証拠保全のため」と説明
- 審査時の説明: レビューノートで用途を詳しく説明
<?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>
【アプリの目的】 このアプリは、パワハラ・セクハラ・DV被害者が 自己防衛のために証拠を記録するためのツールです。 【主な機能】 1. 高音質録音 2. ファイル暗号化(AES-256) 3. 自動クラウドバックアップ 4. タイムスタンプ・位置情報記録 5. 証明書発行機能 【法的コンプライアンス】 - 利用規約で「自分が会話の当事者である場合のみ使用可能」と明記 - 違法な盗聴には使用しないよう警告を表示 - プライバシーポリシーで全データの取り扱いを説明 【偽装機能について】 電卓・時計モードは、加害者に録音を気づかれずに 被害者が安全に証拠を残すための機能です。 第三者の会話を盗聴する目的ではありません。 【テストアカウント】 Email: test@stealthrec.com Password: TestPass123! 録音を開始するには、電卓画面で「AC」ボタンを5秒長押ししてください。
💡 審査のコツ: 「護身用ツール」であることを前面に出し、社会的意義を強調しましょう。弁護士監修の利用規約があると信頼性が高まります。
月額680円 × 3,000人 = 月間売上204万円
月額680円 × 10,000人 = 月間売上680万円
→ 初期投資を3〜6ヶ月で回収可能
• 偽装画面は本物として完全に機能させる
• 録音中のインジケーターは極小(1ピクセルの赤点など)
• 通知音・バイブは一切なし
• バッテリー消費も通常アプリと変わらない程度に
2. 社会的意義を前面に
• 「被害者を守るツール」として位置づける
• パワハラ・セクハラ被害の実態を説明
• 弁護士や支援団体と提携してPR
• メディアに取り上げてもらう(社会問題として)
3. 法的リスクの回避
• 利用規約で違法使用を明確に禁止
• 「自分が会話の当事者である場合のみ使用可能」と明記
• 弁護士監修の規約を用意
• アプリ起動時に必ず警告画面を表示
4. 証拠としての信頼性
• タイムスタンプ、位置情報、ハッシュ値で改ざん防止
• 証明書発行機能で法廷でも使える
• クラウドバックアップで端末破壊にも対応
• 弁護士への共有機能を実装
5. ターゲットを明確に
• パワハラ被害者、DV被害者、詐欺被害防止
• 女性向けマーケティングが効果的
• SNSで体験談(匿名)をシェア
• 「いざという時の保険」として訴求
→ 解決策: 「護身用ツール」であることを明確に説明。レビューノートで詳細に用途を記載。
失敗2: 偽装がバレバレ
→ 解決策: 電卓は実際に計算できる、時計は正確な時刻を表示。本物として完全に機能させる。
失敗3: バッテリーがすぐ切れる
→ 解決策: 録音品質を調整可能に。省電力モードを実装。バッテリー警告を出す。
失敗4: 音質が悪くて使えない
→ 解決策: 44.1kHz AAC形式で高音質録音。ノイズキャンセリング機能を追加。
失敗5: 法的トラブル
→ 解決策: 利用規約を弁護士に監修してもらう。違法使用は即アカウント停止。
