Android hidden app data にノートを保存するアプリがあります。質問が簡単になるようにノートをエクスポートしたい:
特定のアプリについて、Googleドライブの非表示のアプリデータにアクセスするにはどうすればよいですか?
実際、Googleでは、この非表示のapp-dataフォルダーに直接アクセスすることはできません。
ただし、Googleのサーバーに対する認証に使用されるアプリのクライアントID /クライアントシークレット/デジタル署名を取得できる場合-はい、基本的にアプリをエミュレートし、Drive APIを使用してGoogleドライブの非表示データにアクセスできます。
通常、AndroidアプリケーションがGoogle API( ドライブ 、ゲーム、またはGoogleサインイン-すべてがサポートされているわけではありません)にアクセスしたい場合)-と通信します- Google Playサービスクライアントライブラリ 、アプリに代わってGoogleからアクセストークンを取得します。このアクセストークンはAPIへの各リクエストとともに送信されるため、Googleはそれを使用しているユーザーと彼はあなたのアカウントで行うことができます( OAuth 2. )。このアクセストークンを初めて取得するために、Google Play開発者サービスはHTTPS POST=リクエストを送信しますこれらのフィールドを持つAndroid.clients.google.com/auth
に(他の詳細とともに):
Token
-Googleアカウントを識別し、基本的にそのアカウントへのフルアクセスを許可する「マスタートークン」app
-com.whatsapp
などのアプリケーションパッケージ名client_sig
-アプリケーションのデジタル署名(SHA1として送信)device
-デバイスの Android IDservice
- スコープ (許可)アプリが持っていることを望んでいるそのため、特定のアプリの名前でDrive APIの使用を開始する前に、その署名とアカウントのマスタートークンを知る必要があります。幸いなことに、署名は.apk
ファイルから簡単に抽出できます。
Shell> unzip whatsapp.apk META-INF/*
Archive: whatsapp.apk
inflating: META-INF/MANIFEST.MF
inflating: META-INF/WHATSAPP.SF
inflating: META-INF/WHATSAPP.DSA
Shell> cd META-INF
Shell> keytool -printcert -file WHATSAPP.DSA # can be CERT.RSA or similar
.....
Certificate fingerprints:
SHA1: 38:A0:F7:D5:05:FE:18:FE:C6:4F:BF:34:3E:CA:AA:F3:10:DB:D7:99
Signature algorithm name: SHA1withDSA
Version: 3
次に必要なのはマスタートークンです。通常、この特別なトークンは、同じURLに対して同様のリクエストを行うことにより、新しいgoogleアカウントが追加されたとき(たとえば、電話を最初にセットアップしたとき)にデバイスに受信され、保存されます。違いは、許可を求めているアプリはPlayサービスアプリ自体(com.google.Android.gms
)であり、Googleにはログインするための追加のEmail
およびPasswd
パラメーターも与えられることです。リクエストが成功した場合、マスタートークンを取得し、ユーザーのアプリリクエストに追加できます。
認証プロセスの詳細については、 this blogpost をご覧ください。
これで、これら2つのHTTPリクエストを直接使用して認証用のコードを作成できます。これは、Googleアカウントでアプリのファイルを閲覧できるコードです。好みのプログラミング言語と クライアントライブラリ を選択するだけです。 [〜#〜] php [〜#〜] :
require __DIR__ . '/vendor/autoload.php'; // Google Drive API
// HTTPS Authentication
$masterToken = getMasterTokenForAccount("[email protected]", "your_password");
$appSignature = '38a0f7d505fe18fec64fbf343ecaaaf310dbd799';
$appID = 'com.whatsapp';
$accessToken = getGoogleDriveAccessToken($masterToken, $appID, $appSignature);
if ($accessToken === false) return;
// Initializing the Google Drive Client
$client = new Google_Client();
$client->setAccessToken($accessToken);
$client->addScope(Google_Service_Drive::DRIVE_APPDATA);
$client->addScope(Google_Service_Drive::DRIVE_FILE);
$client->setClientId(""); // client id and client secret can be left blank
$client->setClientSecret(""); // because we're faking an Android client
$service = new Google_Service_Drive($client);
// Print the names and IDs for up to 10 files.
$optParams = array(
'spaces' => 'appDataFolder',
'fields' => 'nextPageToken, files(id, name)',
'pageSize' => 10
);
$results = $service->files->listFiles($optParams);
if (count($results->getFiles()) == 0)
{
print "No files found.\n";
}
else
{
print "Files:\n";
foreach ($results->getFiles() as $file)
{
print $file->getName() . " (" . $file->getId() . ")\n";
}
}
/*
$fileId = '1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0';
$content = $service->files->get($fileId, array('alt' => 'media' ));
echo var_dump($content);
*/
function getGoogleDriveAccessToken($masterToken, $appIdentifier, $appSignature)
{
if ($masterToken === false) return false;
$url = 'https://Android.clients.google.com/auth';
$deviceID = '0000000000000000';
$requestedService = 'oauth2:https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file';
$data = array('Token' => $masterToken, 'app' => $appIdentifier, 'client_sig' => $appSignature, 'device' => $deviceID, 'google_play_services_version' => '8703000', 'service' => $requestedService, 'has_permission' => '1');
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
{
/* Handle error */
print 'An error occured while requesting an access token: ' . $result . "\r\n";
return false;
}
$startsAt = strpos($result, "Auth=") + strlen("Auth=");
$endsAt = strpos($result, "\n", $startsAt);
$accessToken = substr($result, $startsAt, $endsAt - $startsAt);
return "{\"access_token\":\"" . $accessToken . "\", \"refresh_token\":\"TOKEN\", \"token_type\":\"Bearer\", \"expires_in\":360000, \"id_token\":\"TOKEN\", \"created\":" . time() . "}";
}
function getMasterTokenForAccount($email, $password)
{
$url = 'https://Android.clients.google.com/auth';
$deviceID = '0000000000000000';
$data = array('Email' => $email, 'Passwd' => $password, 'app' => 'com.google.Android.gms', 'client_sig' => '38918a453d07199354f8b19af05ec6562ced5788', 'parentAndroidId' => $deviceID);
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close",
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => TRUE,
'protocol_version'=>'1.1',
//'proxy' => 'tcp://127.0.0.1:8080', // optional proxy for debugging
//'request_fulluri' => true
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if (strpos($http_response_header[0], '200 OK') === false)
{
/* Handle error */
print 'An error occured while trying to log in: ' . $result . "\r\n";
return false;
}
$startsAt = strpos($result, "Token=") + strlen("Token=");
$endsAt = strpos($result, "\n", $startsAt);
$token = substr($result, $startsAt, $endsAt - $startsAt);
return $token;
}
そして最後に、結果-
Files:
gdrive_file_map (1d9QxgC3p4PTXRm_fkAY0OOuTGAckykmDfFls5bAyE1rp)
Databases/msgstore.db.crypt9 (1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0)
16467702039-invisible (1yHFaxfmuB5xRQHLyRfKlUCVZDkgT1zkcbNWoOuyv1WAR)
Done.
注:これは非公式でハッキングされたソリューションであるため、いくつかの問題がある可能性があります。たとえば、アクセストークンは1時間のみ有効であり、その後は自動的に更新されません。
ユーザーは非表示のアプリフォルダー内のデータに直接アクセスできません。アプリのみがデータにアクセスできます。これは、ユーザーが直接操作してはならない構成またはその他の非表示データ用に設計されています。 (ユーザーはデータを削除して、使用するスペースを解放することができます。)
ユーザーがアクセスできる唯一の方法は、特定のアプリで公開されている機能を使用することです。
アプリデータ内のすべてのファイルを取得するには、コードを試してください
private void listFiles() {
Query query =
new Query.Builder()
.addFilter(Filters.or(Filters.eq(SearchableField.MIME_TYPE, "text/html"),
Filters.eq(SearchableField.MIME_TYPE, "text/plain")))
.build();
getDriveResourceClient()
.query(query)
.addOnSuccessListener(this,
new OnSuccessListener<MetadataBuffer>() {
@Override
public void onSuccess(MetadataBuffer metadataBuffer) {
//mResultsAdapter.append(metadataBuffer);
for (int i = 0; i <metadataBuffer.getCount() ; i++) {
retrieveContents(metadataBuffer.get(i).getDriveId().asDriveFile());
}
}
}
)
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "Error retrieving files", e);
MainActivity.this.finish();
}
});
}
また、次のコードでファイルの内容をダウンロードできます
public void retrieveContents(DriveFile file) {
Task<DriveContents> openFileTask =
getDriveResourceClient().openFile(file, DriveFile.MODE_READ_ONLY);
openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() {
@Override
public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception {
DriveContents contents = task.getResult();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(contents.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
Log.e("result ", builder.toString());
}
Task<Void> discardTask = MainActivity.this.getDriveResourceClient().discardContents(contents);
// [END drive_Android_discard_contents]
return discardTask;
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
}
});
}