J'ai une application Android qui stocke mes notes dans données d'application masquées . Je veux exporter mes notes, la question est donc simple:
Comment puis-je accéder aux données d'application masquées dans Google Drive pour une application spécifique?
En effet, Google ne vous permet pas d'accéder directement à ce dossier de données d'application masqué.
Toutefois, si vous pouvez mettre la main sur l'ID client/le secret client/la signature numérique de l'application utilisés pour l'authentification auprès des serveurs de Google, vous pouvez émuler l'application et accéder aux données cachées dans votre Google Drive à l'aide de l'API Drive. .
Habituellement, lorsqu'une application Android souhaite accéder à une API Google (telle que Drive , Jeux ou Google Sign-In - toutes ne sont pas prises en charge), elle communique avec la bibliothèque client des services Google Play , qui à son tour. obtient un jeton d'accès de Google pour le compte de l'application. Ce jeton d'accès est ensuite envoyé avec chaque demande à l'API, afin que Google sache qui l'utilise et ce qu'il est autorisé à faire avec votre compte ( OAuth 2.0 ). Afin d'obtenir ce jeton d'accès pour la première fois, le service Google Play envoie une demande HTTPS POST à Android.clients.google.com/auth
avec ces champs (ainsi que d'autres détails):
Token
- un "jeton principal" qui identifie le compte Google et en permet fondamentalement l'accès.app
- le nom du package d'application, tel que com.whatsapp
client_sig
- signature numérique de l'application (envoyé en tant que SHA1)device
- l'identifiant de l'appareil Android IDservice
- les étendues (autorisations) que l'application veut avoirPar conséquent, avant de pouvoir commencer à utiliser l'API Drive au nom d'une application spécifique, nous devons connaître sa signature et le jeton principal de notre compte. Heureusement, la signature peut être facilement extraite du fichier .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
La prochaine chose dont nous avons besoin est le jeton maître. Ce jeton spécial est normalement reçu et stocké sur l'appareil lorsqu'un nouveau compte Google est ajouté (par exemple, lors de la première configuration du téléphone), en envoyant une demande similaire à la même adresse URL. La différence est que l’application qui demande des autorisations est désormais l’application Services de lecture elle-même (com.google.Android.gms
) et que Google se voit également attribuer des paramètres Email
et Passwd
supplémentaires avec lesquels se connecter. Si la demande aboutit, nous récupérerons notre jeton principal, qui pourrait ensuite être ajouté à la demande d'application de l'utilisateur.
Vous pouvez lire cet article de blog pour plus d'informations sur le processus d'authentification.
Maintenant, nous pouvons écrire un code pour l'authentification en utilisant directement ces deux requêtes HTTP - un code permettant de parcourir les fichiers de n'importe quelle application avec n'importe quel compte Google. Il suffit de choisir votre langage de programmation préféré et bibliothèque client . Je trouvais cela plus facile avec 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;
}
Et enfin, les résultats -
Files:
gdrive_file_map (1d9QxgC3p4PTXRm_fkAY0OOuTGAckykmDfFls5bAyE1rp)
Databases/msgstore.db.crypt9 (1kTFG5TmgIGTPJuVynWfhkXxLPgz32QnPJCe5jxL8dTn0)
16467702039-invisible (1yHFaxfmuB5xRQHLyRfKlUCVZDkgT1zkcbNWoOuyv1WAR)
Done.
NOTE: Ceci est une solution non officielle, hacky, et peut donc avoir quelques problèmes. Par exemple, le jeton d'accès n'est actif que pendant une heure, après quoi il ne sera pas actualisé automatiquement.
L'utilisateur ne peut pas accéder directement aux données des dossiers d'application masqués, seule l'application peut y accéder. Ceci est conçu pour la configuration ou d'autres données cachées que l'utilisateur ne doit pas manipuler directement. (L'utilisateur peut choisir de supprimer les données pour libérer de l'espace.)
L'utilisateur peut uniquement y accéder via certaines fonctionnalités exposées par l'application spécifique.
pour obtenir tous les fichiers dans les données de l'application, essayez le code
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();
}
});
}
aussi vous pouvez télécharger le contenu du fichier bye le code suivant
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) {
}
});
}