mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-20 06:29:02 +00:00
Add support for decrypting and saving KeeLoq signals for SubGhzD (#2982)
This commit is contained in:
@@ -25,6 +25,9 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "../keeloq_keystore.hpp"
|
||||
#include "../keeloq_file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
@@ -56,6 +59,38 @@ void SubGhzDRecentEntryDetailView::update_data() {
|
||||
if (cnt != SD_NO_CNT) console.writeln("Cnt: " + to_string_dec_uint(cnt));
|
||||
|
||||
if (entry_.data != 0) console.writeln("Data: " + to_string_hex(entry_.data));
|
||||
|
||||
if (entry_.sensorType == FPS_KEELOQ) {
|
||||
console.writeln("Fix: " + to_string_hex(fix));
|
||||
console.writeln("Encrypted: " + to_string_hex(encrypted));
|
||||
console.writeln("Manufacturer: " + mf_name);
|
||||
|
||||
if (hop != SD_NO_HOP) {
|
||||
console.writeln("Hop: " + to_string_hex(hop));
|
||||
|
||||
add_children({&button_save});
|
||||
|
||||
button_save.on_select = [this](const Button&) {
|
||||
keeloq_file_buffer.clear();
|
||||
|
||||
text_prompt(
|
||||
nav_,
|
||||
keeloq_file_buffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
KeeloqData params{
|
||||
mf_name,
|
||||
serial,
|
||||
cnt,
|
||||
btn};
|
||||
|
||||
ensure_directory(keeloq_remotes_dir);
|
||||
write_keeloq_file(keeloq_remotes_dir / buffer + ".KEELOQ", params);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzDRecentEntryDetailView::SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry)
|
||||
@@ -303,6 +338,51 @@ void atomo_decrypt(uint8_t* buff) {
|
||||
}
|
||||
}
|
||||
|
||||
bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt(uint32_t decrypt) {
|
||||
uint16_t end_serial = serial & 0xFF;
|
||||
|
||||
if ((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) ||
|
||||
((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) {
|
||||
cnt = decrypt & 0xFFFF;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt_centurion(uint32_t decrypt) {
|
||||
if ((decrypt >> 28 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x3FF) == 0x1CE)) {
|
||||
cnt = decrypt & 0xFFFF;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t keeloq_decrypt(const uint32_t data, const uint64_t key) {
|
||||
uint32_t x = data, r;
|
||||
for (r = 0; r < 528; r++)
|
||||
x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^
|
||||
bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30));
|
||||
return x;
|
||||
}
|
||||
|
||||
uint64_t keeloq_normal_learning(uint32_t data, const uint64_t key) {
|
||||
uint32_t k1, k2;
|
||||
|
||||
data &= 0x0FFFFFFF;
|
||||
data |= 0x20000000;
|
||||
k1 = keeloq_decrypt(data, key);
|
||||
|
||||
data &= 0x0FFFFFFF;
|
||||
data |= 0x60000000;
|
||||
k2 = keeloq_decrypt(data, key);
|
||||
|
||||
return ((uint64_t)k2 << 32) | k1;
|
||||
}
|
||||
|
||||
const uint32_t came_twee_magic_numbers_xor[15] = {
|
||||
0x0E0E0E00,
|
||||
0x1D1D1D11,
|
||||
@@ -591,7 +671,57 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
|
||||
}
|
||||
|
||||
if (entry_.sensorType == FPS_KEELOQ) {
|
||||
// too many sub protocol versions, skipping. maybe in future when we'll have much more fw space
|
||||
uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64);
|
||||
|
||||
btn = data_rev >> 60;
|
||||
serial = (data_rev >> 32) & 0xFFFFFFF;
|
||||
fix = data_rev >> 32;
|
||||
encrypted = data_rev & 0xFFFFFFFF;
|
||||
|
||||
KeeloqKeystore keystore{};
|
||||
|
||||
const auto& keys = keystore.get_keys();
|
||||
|
||||
if (keys.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& key : keys) {
|
||||
switch (key.type) {
|
||||
case KEELOQ_SIMPLE_LEARNING: {
|
||||
uint32_t decrypted = keeloq_decrypt(encrypted, key.key);
|
||||
|
||||
if (keeloq_check_decrypt(decrypted)) {
|
||||
mf_name = key.mf_name;
|
||||
hop = decrypted;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KEELOQ_NORMAL_LEARNING: {
|
||||
uint64_t man = keeloq_normal_learning(fix, key.key);
|
||||
uint32_t decrypted = keeloq_decrypt(encrypted, man);
|
||||
|
||||
if (key.mf_name == "Centurion" && keeloq_check_decrypt_centurion(decrypted)) {
|
||||
mf_name = "Centurion";
|
||||
hop = decrypted;
|
||||
|
||||
return;
|
||||
} else if (keeloq_check_decrypt(decrypted)) {
|
||||
mf_name = key.mf_name;
|
||||
hop = decrypted;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,17 @@
|
||||
|
||||
#define SD_NO_SERIAL 0xFFFFFFFF
|
||||
#define SD_NO_BTN 0xFF
|
||||
#define SD_NO_CNT 0xFF
|
||||
#define SD_NO_CNT 0xFFFFFFFF
|
||||
|
||||
#define SD_NO_HOP 0xFFFFFFFF
|
||||
#define SD_NO_FIX 0xFFFFFFFF
|
||||
#define SD_NO_ENCRYPTED 0xFFFFFFFF
|
||||
|
||||
#define bit(x, n) (((x) >> (n)) & 1)
|
||||
#define g5(x, a, b, c, d, e) \
|
||||
(bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16)
|
||||
|
||||
#define KEELOQ_NLF 0x3A5C742E
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -191,6 +201,19 @@ class SubGhzDRecentEntryDetailView : public View {
|
||||
uint32_t cnt = SD_NO_CNT;
|
||||
uint32_t seed = 0;
|
||||
|
||||
// keeloq specific
|
||||
std::string keeloq_file_buffer{};
|
||||
|
||||
uint32_t hop = SD_NO_HOP;
|
||||
uint32_t fix = SD_NO_FIX;
|
||||
uint32_t encrypted = SD_NO_ENCRYPTED;
|
||||
|
||||
std::string mf_name = "Unknown";
|
||||
|
||||
bool keeloq_check_decrypt(uint32_t decrypt);
|
||||
bool keeloq_check_decrypt_centurion(uint32_t decrypt);
|
||||
// end keeloq specific
|
||||
|
||||
Text text_type{{UI_POS_X(0), 1 * 16, 15 * 8, 16}, "?"};
|
||||
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
|
||||
|
||||
@@ -207,6 +230,10 @@ class SubGhzDRecentEntryDetailView : public View {
|
||||
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
|
||||
"Done"};
|
||||
|
||||
Button button_save{
|
||||
{4, screen_height - 32 - 12, 96, 32},
|
||||
"Save"};
|
||||
|
||||
void parseProtocol();
|
||||
};
|
||||
|
||||
|
||||
@@ -53,3 +53,5 @@ const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
|
||||
const std::filesystem::path macaddress_dir = u"MACADDRESS";
|
||||
const std::filesystem::path keeloq_keys_dir = u"KEELOQKEYS";
|
||||
const std::filesystem::path keeloq_remotes_dir = u"KEELOQREMOTES";
|
||||
|
||||
@@ -55,5 +55,7 @@ extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
extern const std::filesystem::path waterfalls_dir;
|
||||
extern const std::filesystem::path macaddress_dir;
|
||||
extern const std::filesystem::path keeloq_keys_dir;
|
||||
extern const std::filesystem::path keeloq_remotes_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2026 lifegame1lu111
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "keeloq_file.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
bool read_keeloq_file(const fs::path& path, KeeloqData& data) {
|
||||
File file{};
|
||||
|
||||
auto result = file.open(path);
|
||||
|
||||
if (result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileLineReader reader{file};
|
||||
|
||||
std::string raw = *reader.begin();
|
||||
auto chunks = split_string(raw, ';');
|
||||
|
||||
if (chunks.size() != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data.mf_name = std::string{chunks[0]};
|
||||
data.serial = std::strtoul(chunks[1].data(), NULL, 16);
|
||||
data.counter = (uint16_t)std::atoi(chunks[2].data());
|
||||
data.btn = (uint8_t)std::atoi(chunks[3].data());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_keeloq_file(const fs::path& path, const KeeloqData& data) {
|
||||
delete_file(path);
|
||||
|
||||
File file{};
|
||||
|
||||
auto result = file.open(path, false, true);
|
||||
|
||||
if (result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string formatted = data.mf_name + ";" + to_string_hex(data.serial) + ";" + to_string_dec_uint(data.counter) + ";" + to_string_dec_uint(data.btn);
|
||||
|
||||
file.write_line(formatted);
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2026 lifegame1lu111
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __KEELOQ_FILE__
|
||||
#define __KEELOQ_FILE__
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include <string_view>
|
||||
|
||||
struct KeeloqData {
|
||||
std::string mf_name{};
|
||||
uint32_t serial = 0;
|
||||
uint16_t counter = 0;
|
||||
uint8_t btn = 0;
|
||||
};
|
||||
|
||||
bool read_keeloq_file(const std::filesystem::path&, KeeloqData&);
|
||||
bool write_keeloq_file(const std::filesystem::path&, const KeeloqData&);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2026 lifegame1lu111
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "keeloq_keystore.hpp"
|
||||
|
||||
KeeloqKeystore::KeeloqKeystore() {
|
||||
ensure_directory(keeloq_keys_dir);
|
||||
|
||||
File keeloq_keys_file{};
|
||||
|
||||
if (keeloq_keys_file.open(keeloq_keys_dir / "MFCODES")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileLineReader reader{keeloq_keys_file};
|
||||
|
||||
for (const std::string& line : reader) {
|
||||
auto cols = split_string(line, ';');
|
||||
|
||||
if (cols.size() != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeeloqKey key{
|
||||
std::string{cols[0]},
|
||||
std::strtoull(cols[1].data(), NULL, 16),
|
||||
(uint8_t)std::atoi(cols[2].data())};
|
||||
|
||||
keys.push_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<KeeloqKey>& KeeloqKeystore::get_keys() {
|
||||
return keys;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2026 lifegame1lu111
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __KEELOQ_KEYSTORE__
|
||||
#define __KEELOQ_KEYSTORE__
|
||||
|
||||
#include "file_reader.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include <string_view>
|
||||
|
||||
#define KEELOQ_SIMPLE_LEARNING 1
|
||||
#define KEELOQ_NORMAL_LEARNING 2
|
||||
|
||||
struct KeeloqKey {
|
||||
std::string mf_name{};
|
||||
uint64_t key = 0;
|
||||
uint8_t type = 0;
|
||||
};
|
||||
|
||||
class KeeloqKeystore {
|
||||
public:
|
||||
KeeloqKeystore();
|
||||
|
||||
const std::vector<KeeloqKey>& get_keys() const;
|
||||
|
||||
private:
|
||||
std::vector<KeeloqKey> keys{};
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user