diff --git a/src/libs/plugins/backends/aqhbci/admjobs/Makefile.am b/src/libs/plugins/backends/aqhbci/admjobs/Makefile.am
index 7a5c92b..ff20e49 100644
--- a/src/libs/plugins/backends/aqhbci/admjobs/Makefile.am
+++ b/src/libs/plugins/backends/aqhbci/admjobs/Makefile.am
@@ -14,6 +14,7 @@ noinst_LTLIBRARIES=libhbciadmjobs.la
 noinst_HEADERS=\
   jobgetkeys_l.h jobgetkeys_p.h \
   jobsendkeys_l.h jobsendkeys_p.h \
+  jobchangekeys_l.h jobchangekeys_p.h \
   jobtan_l.h jobtan_p.h \
   jobgetsepainfo_l.h jobgetsepainfo_p.h \
   jobgetsysid_l.h jobgetsysid_p.h \
@@ -22,6 +23,7 @@ noinst_HEADERS=\
 libhbciadmjobs_la_SOURCES=\
   jobgetkeys.c \
   jobsendkeys.c \
+  jobchangekeys.c \
   jobtan.c \
   jobgetsepainfo.c \
   jobgetsysid.c \
@@ -40,6 +42,7 @@ sources:
 xmlfiles=\
   $(srcdir)/jobgetkeys.xml \
   $(srcdir)/jobsendkeys.xml \
+  $(srcdir)/jobchangekeys.xml \
   $(srcdir)/jobtan.xml \
   $(srcdir)/jobgetsepainfo.xml \
   $(srcdir)/jobgetsysid.xml \
diff --git a/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys.c b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys.c
new file mode 100644
index 0000000..6ee8e4e
--- /dev/null
+++ b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys.c
@@ -0,0 +1,1276 @@
+/***************************************************************************
+
+ ***************************************************************************
+ *          Please see toplevel file COPYING for license details           *
+ ***************************************************************************/
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#include "jobchangekeys_p.h"
+#include "../banking/user_l.h"
+#include "../banking/provider_l.h"
+#include <gwenhywfar/ctplugin.h>
+#include <gwenhywfar/ctfile_be.h>
+
+#include <stdarg.h>
+#include <unistd.h>
+
+
+/*
+  Mögliche Wechsel:
+
+  medium			type	mode
+
+  datei				RDH		1		karte o. cert	RAH	9
+  datei				RDH		1		datei			RAH	10
+  karte o. cert		RDH		1		karte o. cert	RAH	9
+  datei				RDH		2		karte o. cert	RAH	9
+  datei				RDH		2		datei			RAH	10
+  karte o. cert		RDH		5		karte o. cert	RAH	9
+
+  ohne Schlüsselwechsel:
+  karte o. cert		RDH		9		karte o. cert	RAH	9
+  datei				RDH		10		karte o. cert	RAH	9
+  datei				RDH		10		datei			RAH	10
+*/
+
+
+GWEN_INHERIT(AH_JOB, AH_JOB_CHANGEKEYS)
+
+const char *fmtStr(char *buff, size_t buffLen, const char *fmt, ...)  __attribute__ ((format (printf, 3, 4)));
+const char *fmtStr(char *buff, size_t buffLen, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	vsnprintf(buff, buffLen - 1, fmt, args);
+	buff[buffLen - 1] = 0;
+	va_end(args);
+	return buff;
+}
+
+#define FB fmtBuff, sizeof(fmtBuff)
+
+int onError(const char *m, int rv)
+{
+	DBG_ERROR(AQHBCI_LOGDOMAIN, "%s", m);
+	GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_ERROR,
+				I18N("Change keys: error"), I18N(m), I18N("OK"), NULL, NULL, 0);
+	return rv;
+}
+
+
+struct AH_JOB_CHANGEKEYS
+{
+	uint16_t flags;
+	uint8_t *canceled;
+	AB_PROVIDER *pro;
+	AB_USER *u;
+	AB_USER *uTmp;
+	const char *fm;
+	char *tt, *tn;
+	uint32_t kvVCurr, kvSCurr, kvACurr;
+	GWEN_CRYPT_TOKEN *ct;
+	const GWEN_CRYPT_TOKEN_CONTEXT *ctx;
+	int tokenCtxId;
+	const GWEN_CRYPT_TOKEN_KEYINFO *kiV, *kiS, *kiA;
+	int8_t resp;
+	GWEN_BUFFER *emsg;
+};
+
+const char *strUpper(char *s)
+{
+	size_t l = strlen(s);
+	for(size_t i = 0; i < l; i++)
+	{
+		if((s[i] >= 'a') && (s[i] <= 'z'))
+			s[i] -= 0x20;
+
+	}
+	return s;
+}
+
+int8_t getKeyInfo(AH_HBCI *h, const char *tt, const char *tn, uint32_t cid, GWEN_CRYPT_TOKEN **ct, const GWEN_CRYPT_TOKEN_CONTEXT **ctx,
+				  const GWEN_CRYPT_TOKEN_KEYINFO **kiV, const GWEN_CRYPT_TOKEN_KEYINFO **kiS, const GWEN_CRYPT_TOKEN_KEYINFO **kiA)
+{
+	int8_t res = 0;
+	uint32_t f = GWEN_CRYPT_TOKEN_KEYFLAGS_HASMODULUS | GWEN_CRYPT_TOKEN_KEYFLAGS_HASEXPONENT |
+							GWEN_CRYPT_TOKEN_KEYFLAGS_HASKEYVERSION | GWEN_CRYPT_TOKEN_KEYFLAGS_HASKEYNUMBER;
+	if(!*ct && (((AB_Banking_GetCryptToken(AH_HBCI_GetBankingApi(h), tt, tn, ct) < 0)) || !*ct))
+	{
+		DBG_ERROR(AQHBCI_LOGDOMAIN, "AB_Banking_GetCryptToken() failed (tt '%s', tn '%s').", tt, tn);
+		return -1;
+	}
+	if(GWEN_Crypt_Token_Open(*ct, 0, 0) < 0)
+	{
+		DBG_ERROR(AQHBCI_LOGDOMAIN, "GWEN_Crypt_Token_Open() failed.");
+		return -1;
+	}
+	*ctx = GWEN_Crypt_Token_GetContext(*ct, cid, 0);
+	if(!*ctx)
+	{
+		DBG_ERROR(AQHBCI_LOGDOMAIN, "GWEN_Crypt_Token_GetContext() failed (cid %ld).", (long)cid);
+		return -1;
+	}
+	for(uint8_t i = 0; i < 3; i++)
+	{
+		uint32_t kid = 0;
+		const GWEN_CRYPT_TOKEN_KEYINFO **ki = NULL;
+		char kl = '?';
+		switch(i)
+		{
+		case 0: kid = GWEN_Crypt_Token_Context_GetDecipherKeyId(*ctx); ki = kiV, kl = 'V'; break;
+		case 1: kid = GWEN_Crypt_Token_Context_GetSignKeyId(*ctx); ki = kiS; kl = 'S'; break;
+		case 2: kid = GWEN_Crypt_Token_Context_GetAuthSignKeyId(*ctx); ki = kiA; kl = 'D'; break;
+		}
+		if((kl == 'D') && !strcmp(tt, "ddvcard"))
+			continue;
+		if((*ki = GWEN_Crypt_Token_GetKeyInfo(*ct, kid, f, 0)) == NULL)
+		{
+			DBG_ERROR(AQHBCI_LOGDOMAIN, "GWEN_Crypt_Token_GetKeyInfo() (%c) failed.", kl);
+			res = -1;
+			break;
+		}
+	}
+	return res;
+}
+
+int8_t tokenHasKeys(GWEN_CRYPT_TOKEN *ct, const GWEN_CRYPT_TOKEN_CONTEXT *ctx)
+{
+	int8_t res = 2;
+	for(uint8_t i = 0; (res > 0) && (i < 2); i++)
+	{
+		const GWEN_CRYPT_TOKEN_KEYINFO *ki = NULL;
+		int kn = 0, kv = 0;
+		uint32_t flags = 0;
+		uint32_t id = (i == 0) ? GWEN_Crypt_Token_Context_GetDecipherKeyId(ctx) : GWEN_Crypt_Token_Context_GetVerifyKeyId(ctx);
+		if(res == 2)
+			res = 1;
+		if(!id)
+			res = -1;
+		else
+			ki = GWEN_Crypt_Token_GetKeyInfo(ct, id, 0, 0);
+		if(!ki)
+			res = -1;
+		else
+		{
+			kn = GWEN_Crypt_Token_KeyInfo_GetKeyNumber(ki);
+			kv = GWEN_Crypt_Token_KeyInfo_GetKeyVersion(ki);
+			flags = GWEN_Crypt_Token_KeyInfo_GetFlags(ki);
+		}
+		if((res >= 0) && (!kn || !kv || !(flags & GWEN_CRYPT_TOKEN_KEYFLAGS_HASMODULUS) || !(flags & GWEN_CRYPT_TOKEN_KEYFLAGS_HASEXPONENT)))
+			res = 0;
+		DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): id %ld %d %d m %d e %d -> %d.", __FUNCTION__, (long)id, kn, kv,
+				   (flags & GWEN_CRYPT_TOKEN_KEYFLAGS_HASMODULUS) != 0, (flags & GWEN_CRYPT_TOKEN_KEYFLAGS_HASEXPONENT) != 0, res);
+	}
+	return (res == 1) ? 1 : 0;
+}
+
+#if 1
+int8_t setKeyVersion(GWEN_CRYPT_TOKEN *ct, const GWEN_CRYPT_TOKEN_CONTEXT *ctx, const GWEN_CRYPT_TOKEN_KEYINFO *ki, char t, uint32_t kv)
+{
+	int8_t ret = 0;
+	GWEN_CRYPT_TOKEN_KEYINFO *kin = NULL;
+	uint32_t kid = 0;
+
+	switch(t)
+	{
+	case 'V': kid = GWEN_Crypt_Token_Context_GetDecipherKeyId(ctx); break;
+	case 'S': kid = GWEN_Crypt_Token_Context_GetSignKeyId(ctx); break;
+	case 'A': kid = GWEN_Crypt_Token_Context_GetAuthSignKeyId(ctx); break;
+	default:
+		DBG_ERROR(AQHBCI_LOGDOMAIN, "type %c invalid.", t);
+		return -1;
+	}
+
+	kin = GWEN_Crypt_Token_KeyInfo_dup(ki);
+
+	if(kv > 999)
+		kv = 1;
+	DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): key %ld '%c' set version %ld.", __FUNCTION__, (long)kid, t, (long)kv);
+	GWEN_Crypt_Token_KeyInfo_SetKeyVersion(kin, kv);
+	if(GWEN_Crypt_Token_SetKeyInfo(ct, kid, kin, 0))
+	{
+		DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): GWEN_Crypt_Token_SetKeyInfo() failed.", __FUNCTION__);
+		ret = -1;
+	}
+	GWEN_Crypt_Token_KeyInfo_free(kin);
+	if(ret)
+		return ret;
+	if((ret == 0) && (ki = GWEN_Crypt_Token_GetKeyInfo(ct, kid, 0, 0)) == NULL)
+	{
+		DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): GWEN_Crypt_Token_GetKeyInfo() (%c) failed.", __FUNCTION__, t);
+		ret = -1;
+	}
+	if(ret)
+		return ret;
+	DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): key '%c' version now %d.", __FUNCTION__, t, GWEN_Crypt_Token_KeyInfo_GetKeyVersion(ki));
+	return 0;
+}
+#endif
+
+#define FJCK_CHMEDIA		1
+#define FJCK_CHKEY			2
+#define FJCK_CHPROFILE		4
+#define FJCK_SRCFILE		8
+#define FJCK_DSTFILE		16
+#define FJCK_DSTFILE_EXISTS	32
+
+void GWENHYWFAR_CB GWENHYWFAR_CB AH_Job_ChangeKeys_FreeData(void *bp, void *p);
+int AH_Job_ChangeKeys_NextMsg(AH_JOB *j);
+
+AH_JOB *AH_Job_ChangeKeys_new(AB_PROVIDER *pro, AB_USER *u, GWEN_DB_NODE *args, uint8_t *canceled)
+{
+	int res = 0;
+	char fmtBuff[256];
+	AH_JOB *j = NULL;
+	AH_JOB_CHANGEKEYS *jd = NULL;
+	AB_USER *uTmp = NULL;
+	AH_HBCI *h = AH_Provider_GetHbci(pro);
+	uint16_t flags = FJCK_CHKEY | FJCK_CHMEDIA | FJCK_CHPROFILE;
+	AH_CRYPT_MODE cryptModeNew = AH_CryptMode_None;
+	int cryptTypeNew = -1;
+	int tokenCtxIdNew = -1;
+	const char *tt = NULL;
+	const char *tn = NULL;
+	char *ttn = NULL, *tnn = NULL;
+	const char *scmn = NULL;
+	GWEN_CRYPT_TOKEN *ct = NULL, *ctNew = NULL;
+	const GWEN_CRYPT_TOKEN_CONTEXT *ctx = NULL, *ctxNew = NULL;
+	const GWEN_CRYPT_TOKEN_KEYINFO *kiV = NULL, *kiS = NULL, *kiA = NULL, *kiVNew = NULL, *kiSNew = NULL, *kiANew = NULL;
+	GWEN_PLUGIN *plg = NULL;
+	GWEN_PLUGIN_MANAGER *pm = GWEN_PluginManager_FindPluginManager(GWEN_CRYPT_TOKEN_PLUGIN_TYPENAME);
+	const char *cmn = "?", *fm = "?", *fmn = "?";
+
+	assert(h);
+
+	//GWEN_DB_Dump(args, 0);
+	ttn = strdup(GWEN_DB_GetCharValue(args, "tokenType", 0, ""));
+	tnn = strdup(GWEN_DB_GetCharValue(args, "tokenName", 0, ""));
+	scmn = GWEN_DB_GetCharValue(args, "cryptMode", 0, "");
+	cryptTypeNew = GWEN_DB_GetIntValue(args, "cryptType", 0, -1);
+	tokenCtxIdNew = GWEN_DB_GetIntValue(args, "context", 0, -1);
+
+	if(scmn && *scmn)
+	{
+		if(!strcasecmp(scmn, "RDH"))
+			cryptModeNew = AH_CryptMode_Rdh;
+		else if(!strcasecmp(scmn, "RAH"))
+			cryptModeNew = AH_CryptMode_Rah;
+	}
+
+	tt = AH_User_GetTokenType(u);
+	tn = AH_User_GetTokenName(u);
+	if(!strcasecmp(tt, "ohbci"))
+		flags |= FJCK_SRCFILE;
+	if(!*ttn)
+	{
+		free(ttn);
+		ttn = NULL;
+		if(*tnn && !strchr(tnn, '/') && !strchr(tnn, '\\')) // assume thats a card number
+		{
+			size_t l = strlen(tnn);
+			size_t i = 0;
+			for(; i < l; i++)
+			{
+				if((tnn[i] < '0') || (tnn[i] > '9'))
+					break;
+			}
+			if(i == l)
+			{
+				ttn = strdup("card");
+				if(l < 10)
+				{
+					char *tmp = strdup("0000000000");
+					for(i = 0; i < l; i++)
+						tmp[9 - i] = tnn[(l - 1) - i];
+					free(tnn);
+					tnn = tmp;
+				}
+			}
+		}
+	}
+	if(ttn && !*ttn)
+	{
+		free(ttn);
+		ttn = NULL;
+	}
+	if(!ttn)
+		ttn = strdup(tt);
+	if(!strcasecmp(ttn, "file") || !strcasecmp(ttn, "ohbci"))
+	{
+		flags |= FJCK_DSTFILE;
+		free(ttn);
+		ttn = strdup("ohbci");
+	}
+	else if(!strstr(ttn, "card"))
+		res = onError(fmtStr(FB, "Ungültiger token-typ '%s'.", ttn), -1);
+	if(!*tnn)
+	{
+		free(tnn);
+		tnn = strdup(tn);
+		if(!strcasecmp(tt, "ohbci"))
+			flags |= FJCK_DSTFILE;
+	}
+
+	if(res == 0)
+	{
+		if(!access(tnn, F_OK))
+			flags |= FJCK_DSTFILE_EXISTS;
+
+		if(!strcasecmp(tt, ttn) && !strcmp(tn, tnn))
+		{
+			if(cryptModeNew == AH_CryptMode_None)
+				cryptModeNew = AH_User_GetCryptMode(u);
+			if(cryptTypeNew < 0)
+				cryptTypeNew = AH_User_GetRdhType(u);
+		}
+		if(cryptModeNew == AH_CryptMode_None)
+			res = onError("Crypt-mode muss angegeben werden.", -1);
+		if((res == 0) && (cryptTypeNew < 0))
+			res = onError("Crypt-typ muss angegeben werden.", -1);
+	}
+
+	if(tokenCtxIdNew < 0)
+		tokenCtxIdNew = 1;
+
+	fm = (flags & FJCK_SRCFILE) ? "Schlüsseldatei" : "Chipkarte";
+	fmn = (flags & FJCK_DSTFILE) ? "Schlüsseldatei" : "Chipkarte";
+
+	DBG_INFO(AQHBCI_LOGDOMAIN, "'%s' '%s' -> '%s' '%s', file %d exists %d'.", tt, tn, ttn, tnn, (flags & FJCK_DSTFILE) != 0, (flags & FJCK_DSTFILE_EXISTS) != 0);
+
+	if(res == 0)
+	{
+		if((((flags & FJCK_SRCFILE) != 1) == ((flags & FJCK_DSTFILE) != 1)) && !strcmp(tn, tnn))
+			res = onError("Schlüsseländerung ohne Medienänderung z.Zt. nicht unterstützt.", -1);
+	}
+
+	if(res == 0)
+	{
+		switch(cryptModeNew)
+		{
+		case AH_CryptMode_Rah: cmn = "RAH"; break;
+		case AH_CryptMode_Rdh: cmn = "RDH"; break;
+		default:
+			cmn = "unknown";
+		}
+		if(!strcasecmp(tt, ttn) && !strcmp(tn, tnn))
+			flags &= ~FJCK_CHMEDIA;
+		if((AH_User_GetCryptMode(u) == cryptModeNew) && (AH_User_GetRdhType(u) == cryptTypeNew))
+			flags &= ~FJCK_CHPROFILE;
+		DBG_INFO(AQHBCI_LOGDOMAIN, "'%s %d' -> '%s' '%s' '%s %d', change: m %d, k %d, p %d.", AH_CryptMode_toString(AH_User_GetCryptMode(u)), AH_User_GetRdhType(u),
+				ttn, tnn, AH_CryptMode_toString(cryptModeNew), cryptTypeNew, (flags & FJCK_CHMEDIA) != 0, (flags & FJCK_CHKEY) != 0, (flags & FJCK_CHPROFILE) != 0);
+		if(flags & FJCK_CHPROFILE)
+		{
+			res = -1;
+			switch(AH_User_GetCryptMode(u))
+			{
+			case AH_CryptMode_Rdh:
+				switch(cryptModeNew)
+				{
+				case AH_CryptMode_Rdh:
+					onError("Änderung des Schlüsselsprofils nach RDH nicht unterstützt.", -1);
+					break;
+				default:
+					if(!(flags & FJCK_SRCFILE) && !(flags & FJCK_DSTFILE) && (AH_User_GetRdhType(u) == 9) && (cryptTypeNew == 9))
+						res = 1;
+					else if((flags & FJCK_SRCFILE) && !(flags & FJCK_DSTFILE) && (AH_User_GetRdhType(u) == 10) && (cryptTypeNew == 9))
+						res = 1;
+					else if((flags & FJCK_SRCFILE) && (flags & FJCK_DSTFILE) && (AH_User_GetRdhType(u) == 10) && (cryptTypeNew == 10))
+						res = 1;
+					if(res == 1)
+					{
+						flags &= ~FJCK_CHKEY;
+						res = 0;
+					}
+					else
+					{
+						if(flags & FJCK_SRCFILE)
+						{
+							switch(AH_User_GetRdhType(u))
+							{
+							case 1:
+							case 2:
+								if(!(flags & FJCK_DSTFILE) && (cryptTypeNew == 9))
+									res = 0;
+								if((flags & FJCK_DSTFILE) && (cryptTypeNew == 10))
+									res = 0;
+								break;
+							default:;
+							}
+						}
+						else
+						{
+							switch(AH_User_GetRdhType(u))
+							{
+							case 1:
+							case 5:
+								if(!(flags & FJCK_DSTFILE) && (cryptTypeNew == 9))
+									res = 0;
+								break;
+							default:;
+							}
+						}
+						if(res)
+							onError(fmtStr(FB, "Änderung des Schlüsselsprofils von %s RDH-%d nach %s RAH-%d nicht unterstützt.",
+								   fm, AH_User_GetRdhType(u), fmn, cryptTypeNew), -1);
+					}
+				}
+				break;
+			default:
+				onError("Änderung des Sicherheitsprofils nur von RDH aus unterstützt.", -1);
+			}
+		}
+		if(res)
+			*canceled = 2;
+	}
+
+	if(res == 0)
+	{
+		// keyinfo current token
+		if(getKeyInfo(h, AH_User_GetTokenType(u), AH_User_GetTokenName(u), AH_User_GetTokenContextId(u), &ct, &ctx, &kiV, &kiS, &kiA)
+				|| !ct || !ctx || !kiV || !kiS || !kiA)
+		{
+			DBG_INFO(AQHBCI_LOGDOMAIN, "getKeyInfo() ct %p, ctx %p, ki %p %p %p.", ct, ctx, kiV, kiS, kiA);
+			if(!ct || !ctx)
+				res = onError("Could not get token.", -1);
+			else
+				res = onError("Sicherheitsmedium für diesen Vorgang nicht geeignet.", -1);
+		}
+	}
+
+	if((res == 0) && (flags & FJCK_CHMEDIA))
+	{
+		char *tokenNew = NULL;
+		if(!strcmp(ttn, "card"))
+		{
+			GWEN_BUFFER *ctn = GWEN_Buffer_new(0, 64, 0, 1);
+			GWEN_BUFFER *cmn = GWEN_Buffer_new(0, 64, 0, 1);
+			for(; res == 0;)
+			{
+				res = AB_Banking_CheckCryptToken(AB_Provider_GetBanking(pro), GWEN_Crypt_Token_Device_Card, ctn, cmn);
+				DBG_INFO(AQHBCI_LOGDOMAIN, "card: '%s' '%s'.", GWEN_Buffer_GetStart(ctn), GWEN_Buffer_GetStart(cmn));
+				if(res)
+					res = onError("AB_Banking_CheckCryptToken() failed.", -1);
+				else
+				{
+					if(tokenNew)
+						free(tokenNew);
+					tokenNew = strdup(GWEN_Buffer_GetStart(ctn));
+					if(!strcmp(tnn, GWEN_Buffer_GetStart(cmn)))
+						break;
+					if(GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: insert card"),
+								fmtStr(FB, "Chipkarte '%s' wird benötigt.", tnn), I18N("Abort"), I18N("OK"), NULL, 0) != 2)
+						res = -2;
+				}
+			}
+			GWEN_Buffer_free(ctn);
+			GWEN_Buffer_free(cmn);
+			if(tokenNew)
+			{
+				free(ttn);
+				ttn = tokenNew;
+				tokenNew = NULL;
+			}
+		}
+
+		if(tokenNew)
+			free(tokenNew);
+
+		if(res == 0)
+			plg = GWEN_PluginManager_GetPlugin(pm, ttn);
+		if(!plg && (res == 0))
+			res = onError(fmtStr(FB, "Could not get plugin for new tokentype '%s'.", ttn), -1);
+		if(plg && (flags & FJCK_DSTFILE))
+		{
+			// diff. context?
+			if((flags & FJCK_SRCFILE) && !strcmp(tn, tnn))
+				res = onError(fmtStr(FB, "Die alte und neue Schlüsseldatei darf nicht identisch sein."), -1);
+			else
+			{
+				uint8_t del = 1;
+				tokenCtxIdNew = 1;
+				if(flags & FJCK_DSTFILE_EXISTS)
+				{
+					del = 0;
+					res = GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+								fmtStr(FB, "Die Schlüsseldatei '%s' existiert bereits.", tnn), I18N("Abort"), I18N("Use"), I18N("Delete"), 0);
+					if(res == 1)
+						res = -1;
+					else if(res == 2) // use
+					{
+						res = 0;
+						//flags &= ~FJCK_CHKEY;
+						if(getKeyInfo(h, ttn, tnn, tokenCtxIdNew, &ctNew, &ctxNew, &kiVNew, &kiSNew, &kiANew)
+								|| !ctNew || !ctxNew || !kiVNew || !kiSNew || !kiANew)
+							res = onError("Could not get token for new keyfile.", -1);
+					}
+					else if(GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+										fmtStr(FB, "Schlüsseldatei '%s' wirklich löschen?", tnn), I18N("Abort"), I18N("Delete"), NULL, 0) != 2)
+						res = -1;
+					else
+					{
+						res = 0;
+						del = 1;
+					}
+					if(res)
+					{
+						res = onError("Canceled.", -1);
+						*canceled = 1;
+					}
+					else if(del)
+						unlink(tnn);
+				}
+				if((res == 0) && del)
+				{
+					if(GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+									fmtStr(FB, "Schlüsseldatei '%s' wird erzeugt.", tnn), I18N("Abort"), I18N("OK"), NULL, 0) != 2)
+					{
+						res = onError("Canceled.", -1);
+						*canceled = 1;
+					}
+					if(res == 0)
+					{
+						ctNew = GWEN_Crypt_Token_Plugin_CreateToken(plg, tnn);
+						if(!ctNew)
+							res = onError(fmtStr(FB, "Could not create crypt token '%s'.", tnn), -1);
+						else if(GWEN_Crypt_Token_Create(ctNew, 0) < 0)
+							res = onError(fmtStr(FB, "Could not create keyfile '%s'.", GWEN_Crypt_Token_GetTokenName(ctNew)), -1);
+						else if(GWEN_Crypt_Token_Close(ctNew, 0, 0))
+							res = onError("Could not close token.", -1);
+					}
+				}
+			}
+		}
+		// keyinfo dest. token
+		if(res == 0)
+		{
+			if(getKeyInfo(h, ttn, tnn, tokenCtxIdNew, &ctNew, &ctxNew, &kiVNew, &kiSNew, &kiANew)
+					|| !ctNew || !ctxNew || !kiVNew || !kiSNew || !kiANew)
+			{
+				DBG_NOTICE(AQHBCI_LOGDOMAIN, "getKeyInfo() ct %p, ctx %p, ki %p %p %p.", ct, ctx, kiV, kiS, kiA);
+				if(!ct || !ctx)
+					res = onError("Could not get token.", -1);
+				else
+					res = onError("Sicherheitsmedium für diesen Vorgang nicht geeignet.", -1);
+			}
+		}
+		if((res == 0) && !(flags & FJCK_DSTFILE))
+		{
+			int tnV = GWEN_Crypt_Token_KeyInfo_GetKeyNumber(kiVNew);
+			int tnS = GWEN_Crypt_Token_KeyInfo_GetKeyNumber(kiSNew);
+			if((cryptTypeNew != tnV) || (cryptTypeNew != tnS))
+			{
+				DBG_ERROR(AQHBCI_LOGDOMAIN, "rdh-type %d, card %d/%d..", cryptTypeNew, tnV, tnS);
+				res = onError("Ziel-Sicherheitsmedium für diesen Vorgang nicht geeignet.", -1);
+			}
+		}
+	}
+#if 1
+	if(res == 0)
+	{
+		// how get key-pair from token?
+		if(!(flags & FJCK_CHKEY) && (flags & FJCK_CHMEDIA))
+			res = onError("Sicherheitsprofilwechsel ohne Sicherheitsmedienwechsel nicht implementiert.", -1);
+	}
+#endif
+	if(res == 0)
+	{
+		if(flags & FJCK_CHMEDIA)
+		{
+			char *cm = strdup(AH_CryptMode_toString(AH_User_GetCryptMode(u)));
+			const char *m = fmtStr(FB, "Wechsel des Sicherheitsmediums von\n    %s '%s', %s-%d\nnach\n    %s '%s', %s-%d\ndurchführen?",
+							   fm, tn, strUpper(cm), AH_User_GetRdhType(u), fmn, tnn, cmn, cryptTypeNew);
+			free(cm);
+			if(GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+																m, I18N("Abort"), I18N("OK"), NULL, 0) != 2)
+			{
+				res = -1;
+				*canceled = 1;
+			}
+		}
+	}
+
+	if(res == 0)
+	{
+		uTmp = AB_Provider_CreateUserObject(pro);
+		AH_User_SetCryptMode(uTmp, cryptModeNew);
+		AH_User_SetRdhType(uTmp, cryptTypeNew);
+		AH_User_SetTokenType(uTmp, ttn);
+		AH_User_SetTokenName(uTmp, tnn);
+		AH_User_SetTokenContextId(uTmp, tokenCtxIdNew);
+		AB_User_SetBankCode(uTmp, AB_User_GetBankCode(u));
+		AH_User_SetHbciVersion(uTmp, AH_User_GetHbciVersion(u));
+		AH_User_SetServerUrl(uTmp, AH_User_GetServerUrl(u));
+
+		DBG_NOTICE(AQHBCI_LOGDOMAIN, "chng k %d p %d m %d, token: open %d ctx %d knr dst %d %d %d.",
+				   (flags & FJCK_CHKEY) != 0, (flags & FJCK_CHPROFILE) != 0, (flags & FJCK_CHMEDIA) != 0, GWEN_Crypt_Token_IsOpen(ctNew), tokenCtxIdNew,
+				 kiVNew ? GWEN_Crypt_Token_KeyInfo_GetKeyNumber(kiVNew) : - 1, kiSNew ? GWEN_Crypt_Token_KeyInfo_GetKeyNumber(kiSNew) : -1, kiANew ? GWEN_Crypt_Token_KeyInfo_GetKeyNumber(kiANew) : -1);
+	}
+
+	if(res == 0)
+	{
+		j = AH_Job_new((flags & FJCK_DSTFILE) ? "JobChangeKeys" : "JobChangeKeysA", pro, u, NULL, 0);
+		DBG_INFO(AQHBCI_LOGDOMAIN, "%s(): j %p u %p '%s'.", __FUNCTION__, j, u, j ? AH_Job_GetCode(j) : "-");
+		if(!j)
+			res = onError("AH_Job_new() failed.", -1);
+	}
+
+	if(res == 0)
+	{
+		GWEN_NEW_OBJECT(AH_JOB_CHANGEKEYS, jd);
+		GWEN_INHERIT_SETDATA(AH_JOB, AH_JOB_CHANGEKEYS, j, jd, AH_Job_ChangeKeys_FreeData);
+
+		AH_Job_SetNextMsgFn(j, AH_Job_ChangeKeys_NextMsg);
+
+		args = AH_Job_GetArguments(j);
+		assert(args);
+	}
+
+	if(res == 0)
+	{
+		char *cm = strdup(AH_CryptMode_toString(cryptModeNew));
+		strUpper(cm);
+		for(uint8_t i = 0; i < 3; i++)
+		{
+			const char *kt = "?";
+			GWEN_DB_NODE *db = NULL;
+
+			if(i < 2)
+			{
+				switch(i)
+				{
+				case 0: kt = "V"; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "getcryptKey"); break;
+				case 1: kt = "S"; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "getsignKey"); break;
+				}
+
+				// HKISA
+				GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "secProfile/code", cm);
+				GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "secProfile/version", cryptTypeNew);
+
+				GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/userid", AH_User_GetPeerId(u));
+				GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/keyType", kt);
+				GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/keynum", 999);
+				GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/keyversion", 999);
+			}
+
+			switch(i)
+			{
+			case 0: kt = "V"; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "setcryptKey"); break;
+			case 1: kt = "S"; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "setsignKey"); break;
+			case 2: kt = "D"; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "setauthKey"); break;
+			}
+			// HKSAK
+			GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "secProfile/code", cm);
+			GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "secProfile/version", cryptTypeNew);
+			GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/userid", AH_User_GetPeerId(u));
+			GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/keyType", kt);
+			GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "keyName/keyType", kt);
+			GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "keyName/userid", AH_User_GetPeerId(u));
+		}
+		free(cm);
+	}
+	if(res)
+	{
+		if(j)
+			AH_Job_free(j);
+		j = NULL;
+		if(res == -2)
+			*canceled = 2;
+	}
+
+	if(jd)
+	{
+		jd->flags = flags;
+		jd->canceled = canceled;
+		jd->pro = pro;
+		jd->u = u;
+		jd->uTmp = uTmp;
+		jd->fm = fmn;
+		jd->tt = ttn;
+		jd->tn = tnn;
+		jd->kvVCurr = kiV ? GWEN_Crypt_Token_KeyInfo_GetKeyVersion(kiV) : 0;
+		jd->kvSCurr = kiS ? GWEN_Crypt_Token_KeyInfo_GetKeyVersion(kiS) : 0;
+		jd->kvACurr = kiA ? GWEN_Crypt_Token_KeyInfo_GetKeyVersion(kiA) : 0;
+		jd->ct = ctNew;
+		jd->ctx = ctxNew;
+		jd->tokenCtxId = tokenCtxIdNew;
+		jd->kiV = kiVNew;
+		jd->kiS = kiSNew;
+		jd->kiA = kiANew;
+		jd->resp = -1;
+		jd->emsg = NULL;
+	}
+
+	if(ctNew)
+		GWEN_Crypt_Token_Close(ctNew, 0, 0);
+	if(ct)
+		GWEN_Crypt_Token_Close(ct, 0, 0);
+
+	return j;
+}
+
+int onServerKeysImported(AH_JOB_CHANGEKEYS *jd)
+{
+	// serverkeys imported (knowing server-keys length is necessary for some key-types when created)
+	// create keys if required
+
+	char fmtBuff[256];
+	AH_HBCI *h = AH_Provider_GetHbci(jd->pro);
+	int res = 0;
+	const char *m = NULL;
+	const char *btn1 = NULL, *btn2 = NULL, *btn3 = NULL;
+	uint8_t ok = 0;
+
+	DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): flags 0x%04X.", __FUNCTION__, jd->flags);
+	if(jd->flags & FJCK_CHKEY)
+	{
+		//if(!jd->kiV || !GWEN_Crypt_Token_KeyInfo_GetKeyNumber(jd->kiV) || !jd->kiS || !GWEN_Crypt_Token_KeyInfo_GetKeyNumber(jd->kiS))
+		if(!tokenHasKeys(jd->ct, jd->ctx))
+		{
+			m = fmtStr(FB, "Neue Schlüssel werden auf %s '%s' erzeugt.", jd->fm, jd->tn);
+			btn1 = "Abort";
+			btn2 = "OK";
+			ok = 2;
+		}
+		else
+		{
+#if 0
+			// TODO check keys are compatible with cryptmode
+			// if so, ...
+			{
+				m = fmtStr(FB, "Auf %s '%s' sind Schlüssel vorhanden,\nsollen dennoch neue Schlüssel erzeugt werden?", fmn, tnn);
+				btn1 = "Abort";
+				btn2 = "No";
+				btn3 = "Yes";
+				ok = 3;
+			}
+			else
+			{
+				m = fmtStr(FB, "Auf %s '%s' vorhandene Schlüssel können nicht verwendet werden,\nneue Schlüssel werden erzeugt.", fmn, tnn);
+				btn1 = "Abort";
+				btn2 = "OK";
+				ok = 2;
+			}
+#else
+			m = fmtStr(FB, "Auf %s '%s' sind Schlüssel vorhanden die verwendet werden können,\n"
+					   "wenn sie zum gewählten Verschlüsselungsverfahren passen.\n"
+					   "Sollen neue Schlüssel erzeugt werden?", jd->fm, jd->tn);
+			btn1 = "Abort";
+			btn2 = "No";
+			btn3 = "Yes";
+			ok = 3;
+#endif
+		}
+		res = GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+																		m, I18N(btn1), I18N(btn2), btn3 ? I18N(btn3) : NULL, 0);
+		if(res == 1)
+		{
+			res = -1;
+			*jd->canceled = 1;
+		}
+		else
+		{
+			if(res == ok)
+			{
+				res = 0;
+				DBG_INFO(AQHBCI_LOGDOMAIN, "creating keys...");
+				res = AH_Provider_CreateKeys(jd->pro, jd->uTmp, 1);
+				if(res)
+					res = onError(fmtStr(FB, "Could not create keys (%d).", res), -1);
+				DBG_INFO(AQHBCI_LOGDOMAIN, "creating keys done.");
+			}
+			else
+				res = 0;
+		}
+	}
+	else
+		res = -1;
+	if((res == 0) && (getKeyInfo(h, jd->tt, jd->tn, jd->tokenCtxId, &jd->ct, &jd->ctx, &jd->kiV, &jd->kiS, &jd->kiA)
+			|| !jd->ct || !jd->ctx || !jd->kiV || !jd->kiS || !jd->kiA))
+		res = onError("Could not get key-info.", -1);
+
+	if(res == 0)
+	{
+#if 1
+		if(!(jd->flags & FJCK_CHPROFILE))
+		{
+			// set keyversion from current token + 1 on dest.-token
+			uint32_t kvV = jd->kvVCurr ? (jd->kvVCurr + 1) : 0;
+			uint32_t kvS = jd->kvSCurr ? (jd->kvSCurr + 1) : 0;
+			uint32_t kvA = jd->kvACurr ? (jd->kvACurr + 1) : 0;
+			DBG_NOTICE(AQHBCI_LOGDOMAIN, "set keyversions %ld -> %ld, %ld -> %ld, %ld -> %ld.",
+							(long)jd->kvVCurr, (long)kvV, (long)jd->kvSCurr, (long)kvS, (long)jd->kvACurr, (long)kvA);
+			if(kvV)
+				setKeyVersion((GWEN_CRYPT_TOKEN*)jd->ct, jd->ctx, jd->kiV, 'V', kvV);
+			if(kvS)
+				setKeyVersion((GWEN_CRYPT_TOKEN*)jd->ct, jd->ctx, jd->kiS, 'S', kvS);
+			if(!(jd->flags & FJCK_DSTFILE) && kvA)
+				setKeyVersion((GWEN_CRYPT_TOKEN*)jd->ct, jd->ctx, jd->kiA, 'A', kvA);
+		}
+#endif
+		if(jd->flags & FJCK_CHKEY)
+		{
+			if(!jd->kiS || !GWEN_Crypt_Token_KeyInfo_GetKeyNumber(jd->kiS))
+			{
+				DBG_INFO(AQHBCI_LOGDOMAIN, "Kein Signierschlüssel.");
+				res = onError("Kein Signierschlüssel auf dem Ziel-medium gefunden.", -1);
+			}
+			else
+			{
+				int sc = GWEN_Crypt_Token_KeyInfo_GetSignCounter(jd->kiS);
+				DBG_INFO(AQHBCI_LOGDOMAIN, "%s(): sig counter %d.", __FUNCTION__, sc);
+				if(sc > 1)
+				{
+					if(!(jd->flags & FJCK_DSTFILE))
+						res = onError("Der Sequenzzähler kann nicht zurückgesetzt werden.", -1);
+					else
+					{
+						res = GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+												"Der Sequenzzähler wird zurückgesetzt.", I18N("Abort"), I18N("OK"), NULL, 0);
+						if(res != 2)
+							res = -1;
+						else
+							res = 0;
+					}
+					if(res == 0)
+					{
+						DBG_INFO(AQHBCI_LOGDOMAIN, "%s(): reset sig counter.", __FUNCTION__);
+						GWEN_Crypt_Token_KeyInfo_SetSignCounter((GWEN_CRYPT_TOKEN_KEYINFO*)jd->kiS, 1);
+					}
+				}
+			}
+		}
+	}
+
+	if(res == 0)
+	{
+		if(getKeyInfo(h, jd->tt, jd->tn, jd->tokenCtxId, &jd->ct, &jd->ctx, &jd->kiV, &jd->kiS, &jd->kiA)
+				|| !jd->ct || !jd->ctx || !jd->kiV || !jd->kiS || !jd->kiA)
+			res = onError("Could not get key-info.", -1);
+	}
+
+	if((res == 0) && !jd->ctx)
+		res = onError("Missing new ctx.", -1);
+
+	return res;
+}
+
+#define RSP_NOSRVRSP	1
+#define RSP_WARN		2
+#define RSP_ERR			3
+
+int8_t parseResponse(AH_JOB *j)
+{
+	int8_t res = 0;
+	int rc = 0;
+	uint8_t gotResp = 0;
+	AH_JOB_CHANGEKEYS *jd = GWEN_INHERIT_GETDATA(AH_JOB, AH_JOB_CHANGEKEYS, j);
+	GWEN_DB_NODE *n = AH_Job_GetResponses(j);
+	if(AH_Job_GetStatus(j) != AH_JobStatusAnswered)
+		rc = -1;
+	assert(n);
+	jd->emsg = GWEN_Buffer_new(NULL, 2048, 0, 0);
+	n = GWEN_DB_GetFirstGroup(n);
+	while(n)
+	{
+		//GWEN_DB_Dump(n, 0);
+		if(!strcmp(GWEN_DB_GroupName(n), "SegResult"))
+		{
+			int mn = GWEN_DB_GetIntValue(n, "security/msgnum", 0, -1);
+			if(mn == 2)
+			{
+				gotResp = 1;
+				if((rc >= 0) && (rc < 9000))
+				{
+					rc = GWEN_DB_GetIntValue(n, "data/SegResult/result/resultcode", 0, -1);
+					if(rc == 3250)
+					{
+						DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): response %d tells us: no HKEND.", __FUNCTION__, rc);
+						res = 1;
+						rc = 0;
+					}
+					else
+					{
+						GWEN_Buffer_AppendString(jd->emsg, GWEN_DB_GetCharValue(n, "data/SegResult/result/text", 0, "?"));
+						GWEN_Buffer_AppendString(jd->emsg, "\n");
+					}
+				}
+				DBG_NOTICE(AQHBCI_LOGDOMAIN, "result %d.", rc);
+				DBG_NOTICE(AQHBCI_LOGDOMAIN, "result '%s'.", GWEN_Buffer_GetStart(jd->emsg));
+			}
+			else
+			{
+				DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): #%d result %d '%s'.", __FUNCTION__,
+						   GWEN_DB_GetIntValue(n, "security/msgnum", 0, -1),
+						   GWEN_DB_GetIntValue(n, "data/SegResult/result/resultcode", 0, -1),
+						   GWEN_DB_GetCharValue(n, "data/SegResult/result/text", 0, "?"));
+				if(GWEN_DB_GetIntValue(n, "data/SegResult/result/resultcode", 0, -1) >= 9000)
+				{
+					rc = GWEN_DB_GetIntValue(n, "data/SegResult/result/resultcode", 0, -1);
+					GWEN_Buffer_AppendString(jd->emsg, GWEN_DB_GetCharValue(n, "data/SegResult/result/text", 0, "?"));
+					GWEN_Buffer_AppendString(jd->emsg, "\n");
+				}
+			}
+		}
+		n = GWEN_DB_GetNextGroup(n);
+	}
+	if(!gotResp)
+		jd->resp = RSP_NOSRVRSP;
+	else
+		jd->resp = (rc < 3000) ? 0 : (rc < 9000) ? RSP_WARN : RSP_ERR;
+	return res;
+}
+
+void GWENHYWFAR_CB GWENHYWFAR_CB AH_Job_ChangeKeys_FreeData(void *bp, void *p)
+{
+	AH_JOB_CHANGEKEYS *jd = (AH_JOB_CHANGEKEYS*)p;
+	DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): %p uTmp %p.", __FUNCTION__, jd, jd->uTmp);
+
+	if(jd->uTmp)
+		AB_User_free(jd->uTmp);
+	if(jd->tt)
+		free(jd->tt);
+	if(jd->tn)
+		free(jd->tn);
+	if(jd->emsg)
+		GWEN_Buffer_free(jd->emsg);
+
+	GWEN_FREE_OBJECT(jd);
+}
+
+int AH_Job_ChangeKeys_NextMsg(AH_JOB *j)
+{
+	int rv = 0;
+	int mn = -1;
+	unsigned int jmn = 0;
+	GWEN_DB_NODE *dbr = NULL;
+	AH_JOB_CHANGEKEYS *jd;
+	assert(j);
+	jmn = AH_Job_GetMsgNum(j);
+	dbr = AH_Job_GetResponses(j);
+	dbr = GWEN_DB_GetFirstGroup(dbr);
+	DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): jmn %d.", __FUNCTION__, jmn);
+	while(dbr)
+	{
+		//GWEN_DB_Dump(dbr, 0);
+		rv = AH_Job_CheckEncryption(j, dbr);
+		if(rv)
+		{
+			DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): AH_Job_CheckEncryption() failed (%d).", __FUNCTION__, rv);
+			return -1;
+		}
+		rv = AH_Job_CheckSignature(j, dbr);
+		if(rv)
+		{
+			DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): AH_Job_CheckSignature() failed (%d).", __FUNCTION__, rv);
+			return -1;
+		}
+		if(!strcasecmp(GWEN_DB_GroupName(dbr), "MsgTail"))
+		{
+			mn = GWEN_DB_GetIntValue(dbr, "security/msgnum", 0, -1);
+			if(mn < 0)
+			{
+				DBG_ERROR(AQHBCI_LOGDOMAIN, "%s: find msgnum failed.", __FUNCTION__);
+			}
+			else if((mn == 1) && (jmn == mn))
+			{
+				const GWEN_CRYPT_KEY *bk = NULL;
+				GWEN_CRYPT_KEY *bkCurrV = NULL, *bkCurrS = NULL;
+				jd = GWEN_INHERIT_GETDATA(AH_JOB, AH_JOB_CHANGEKEYS, j);
+				assert(jd);
+				// get actual serverkeys to restore later
+				bk = AH_User_GetBankPubCryptKey(jd->u);
+				if(bk)
+					bkCurrV = GWEN_Crypt_KeyRsa_dup(bk);
+				bk = AH_User_GetBankPubSignKey(jd->u);
+				if(bk)
+					bkCurrS = GWEN_Crypt_KeyRsa_dup(bk);
+				rv = AH_Job_CommitSystemData(j, 0);
+				if(rv != 0)
+				{
+					DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): AH_Job_CommitSystemData() failed(%d).", __FUNCTION__, rv);
+				}
+				else if(!GWEN_Crypt_Token_IsOpen(jd->ct) && (GWEN_Crypt_Token_Open(jd->ct, 0, 0) < 0))
+					rv = onError("GWEN_Crypt_Token_Open() failed.", -1);
+				else
+				{
+					GWEN_Gui_ProgressLog(0, GWEN_LoggerLevel_Info, I18N("Serverkeys imported."));
+					DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): serverkeys should be imported now.", __FUNCTION__);
+					// some cryptmodes need length of serverkeys
+					// AH_Provider_CreateKeys() reads the length from user-token,
+					// which is the temporary user with the new token,
+					// so store server-keys from actual user (the now imported) on the new token
+					//DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): ctx %p.", __FUNCTION__, jd->ctx);
+					jd->ctx = GWEN_Crypt_Token_GetContext(jd->ct, jd->tokenCtxId, 0);
+					//DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): -> ctx %p.", __FUNCTION__, jd->ctx);
+					for(uint8_t i = 0; (rv == 0) && (i < 2); i++)
+					{
+						const GWEN_CRYPT_TOKEN_KEYINFO *tmp = NULL;
+						uint8_t *m = NULL, *e = NULL;
+						uint32_t ml = 0, el = 0;
+						uint32_t id = 0;
+						bk = NULL;
+						switch(i)
+						{
+						case 0:
+							bk = AH_User_GetBankPubCryptKey(jd->u);
+							id = GWEN_Crypt_Token_Context_GetEncipherKeyId(jd->ctx);
+							AH_User_SetBankPubCryptKey(jd->uTmp, (GWEN_CRYPT_KEY*)bk);
+							break;
+						case 1:
+							bk = AH_User_GetBankPubSignKey(jd->u);
+							id = GWEN_Crypt_Token_Context_GetVerifyKeyId(jd->ctx);
+							AH_User_SetBankPubSignKey(jd->uTmp, (GWEN_CRYPT_KEY*)bk);
+							break;
+						}
+
+						if(!bk)
+						{
+							if(i == 0)
+							{
+								DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): get bankkey failed.", __FUNCTION__);
+								rv = -1;
+							}
+							else
+								DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): bank has no signkey.", __FUNCTION__);
+							continue;
+						}
+
+						ml = GWEN_Crypt_Key_GetKeySize(bk);
+						el = 3;
+
+						if(ml)
+						{
+							m = malloc(ml);
+							e = malloc(el);
+							GWEN_Crypt_KeyRsa_GetModulus(bk, m, &ml);
+							GWEN_Crypt_KeyRsa_GetExponent(bk, e, &el);
+						}
+						tmp = GWEN_Crypt_Token_GetKeyInfo(jd->ct, id, 0, 0);
+						DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): k %ld m %ld %p e %ld %p.", __FUNCTION__, (long)id, (long)ml, m, (long)el, e);
+						if(!tmp)
+						{
+							DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): damn, get keyinfo '%c' failed.", __FUNCTION__, (i == 0) ? 'V' : 'S');
+							rv = onError("GWEN_Crypt_Token_GetKeyInfo() failed.", -1);
+						}
+						if(rv == 0)
+						{
+							GWEN_CRYPT_TOKEN_KEYINFO *ki = GWEN_Crypt_Token_KeyInfo_dup(tmp);
+							uint32_t flags = GWEN_Crypt_Token_KeyInfo_GetFlags(ki);
+							GWEN_Crypt_Token_KeyInfo_SetFlags(ki, flags | GWEN_CRYPT_TOKEN_KEYFLAGS_HASMODULUS | GWEN_CRYPT_TOKEN_KEYFLAGS_HASEXPONENT);
+							GWEN_Crypt_Token_KeyInfo_SetModulus(ki, m, ml);
+							GWEN_Crypt_Token_KeyInfo_SetExponent(ki, e, el);
+							GWEN_Crypt_Token_KeyInfo_SetKeySize(ki, ml);
+							GWEN_Crypt_Token_KeyInfo_SetKeyNumber(ki, GWEN_Crypt_Key_GetKeyNumber(bk));
+							GWEN_Crypt_Token_KeyInfo_SetKeyVersion(ki, GWEN_Crypt_Key_GetKeyVersion(bk));
+							if(GWEN_Crypt_Token_SetKeyInfo(jd->ct, id, ki, 0))
+							{
+								DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): GWEN_Crypt_Token_SetKeyInfo() failed.", __FUNCTION__);
+								rv = -1;
+							}
+							else
+								ki = NULL;
+							if(ki)
+								GWEN_Crypt_Token_KeyInfo_free(ki);
+						}
+						if(m)
+							free(m);
+						if(e)
+							free(e);
+					}
+					if(rv == 0)
+					{
+						rv = onServerKeysImported(jd);
+						if(rv != 0)
+							DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): onServerKeysImported() failed.", __FUNCTION__);
+					}
+					// serverkeys set in job_commit() must restored
+					if(bkCurrV)
+					{
+						AH_User_SetBankPubCryptKey(jd->u, bkCurrV);
+						DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): serverkey 'V' restored.", __FUNCTION__);
+						GWEN_Crypt_Key_free(bkCurrV);
+					}
+					if(bkCurrS)
+					{
+						AH_User_SetBankPubSignKey(jd->u, bkCurrS);
+						DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): serverkey 'S' restored.", __FUNCTION__);
+						GWEN_Crypt_Key_free(bkCurrS);
+					}
+					if(rv == 0)
+					{
+						// update segment-data
+						GWEN_DB_NODE *args = AH_Job_GetArguments(j);
+						assert(args);
+						for(uint8_t i = 0; (rv == 0) && (i < 3); i++)
+						{
+							GWEN_DB_NODE *db = NULL;
+							const GWEN_CRYPT_TOKEN_KEYINFO *ki = NULL;
+							const uint8_t *kd = NULL;
+							uint32_t kdsz = 0;
+							const char *kt = "?";
+							int kn = 0, kv = 0;
+
+							switch(i)
+							{
+							case 0: kt = "V"; ki = jd->kiV; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "setcryptKey"); break;
+							case 1: kt = "S"; ki = jd->kiS; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "setsignKey"); break;
+							case 2: kt = "D"; ki = jd->kiA; db = GWEN_DB_GetGroup(args, GWEN_DB_FLAGS_DEFAULT, "setauthKey"); break;
+							}
+
+							if(!ki)
+							{
+								if(i < 2)
+								{
+									DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): get keyinfo '%s' failed.", __FUNCTION__, kt);
+									rv = -1;
+								}
+								else
+									DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): token has no authkey.", __FUNCTION__);
+								continue;
+							}
+
+							kd = GWEN_Crypt_Token_KeyInfo_GetModulusData(ki);
+							kdsz = GWEN_Crypt_Token_KeyInfo_GetModulusLen(ki);
+							if(!kd || !kdsz)
+							{
+								DBG_ERROR(AQHBCI_LOGDOMAIN, "No modulus in '%s' key.", kt);
+								rv = -1;
+								break;
+							}
+							GWEN_DB_SetBinValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/modulus", kd, kdsz);
+
+							kd = GWEN_Crypt_Token_KeyInfo_GetExponentData(ki);
+							kdsz = GWEN_Crypt_Token_KeyInfo_GetExponentLen(ki);
+							if(!kd || !kdsz)
+							{
+								DBG_ERROR(AQHBCI_LOGDOMAIN, "No exponent in '%s' key.", kt);
+								rv = -1;
+								break;
+							}
+							GWEN_DB_SetBinValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/exponent", kd, kdsz);
+
+							kd = GWEN_Crypt_Token_KeyInfo_GetCertificateData(ki);
+							kdsz = GWEN_Crypt_Token_KeyInfo_GetCertificateLen(ki);
+							if(kd && kdsz)
+							{
+								GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "cert/type", GWEN_Crypt_Token_KeyInfo_GetCertType(ki));
+								GWEN_DB_SetBinValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "cert/cert", kd, kdsz);
+							}
+							else
+								DBG_NOTICE(AQHBCI_LOGDOMAIN, "No cert for '%s' on token.", kt);
+
+							kn = GWEN_Crypt_Token_KeyInfo_GetKeyNumber(ki);
+							kv = GWEN_Crypt_Token_KeyInfo_GetKeyVersion(ki);
+							GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/keynum", kn);
+							GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "key/keyversion", kv);
+							GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "keyName/keynum", kn);
+							GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "keyName/keyversion", kv);
+						}
+					}
+				}
+			}
+			else if((mn == 2) && (jmn == mn))
+			{
+				int8_t resp = parseResponse(j);
+				DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s: resp %d, %s HKEND.", __FUNCTION__, resp, (resp == 1) ? "no" : "next");
+				if(resp == 1) // since 5.99.25 no chance to prevent HKEND :-(
+					return 0;
+			}
+		}
+		dbr = GWEN_DB_GetNextGroup(dbr);
+	}
+	return (rv == 0) ? 1 : -1;
+}
+
+int AH_Job_ChangeKeys_finish(AB_PROVIDER *pro, AH_JOB *job, int res)
+{
+	AH_JOB_CHANGEKEYS *jd = NULL;
+	AB_USER *u = NULL;
+	AB_USER *uTmp = NULL;
+	if(!job)
+		return res;
+	jd = GWEN_INHERIT_GETDATA(AH_JOB, AH_JOB_CHANGEKEYS, job);
+	if(*jd->canceled)
+	{
+		DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): user canceled.", __FUNCTION__);
+		jd->resp = -1;
+	}
+	u = jd->u;
+	uTmp = jd->uTmp;
+
+	res = -1;
+	if(jd->resp >= 0)
+	{
+		char fmtBuff[1024];
+		const char *m = NULL;
+		const char *btn1 = I18N("Abort");
+		const char *btn2 = I18N("Finish");
+		if(jd->resp == RSP_NOSRVRSP)
+		{
+			GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: Error"),
+							"Der Bankserver hat keine Antwort zur Schlüsseländerung gesendet.", I18N("OK"), NULL, NULL, 0);
+			res = -1;
+		}
+		else
+		{
+			if((jd->resp == RSP_WARN) || (jd->resp == RSP_ERR))
+			{
+				m = strdup(fmtStr(FB, "Der Bankserver meldet %s:\n\n'%s'\n\n"
+						   "Es ist möglich, dass der neue Schlüssel dennoch angenommenn wurde.\n"
+						   "Neue Schlüssel / neues Medium übernehmen?", (jd->resp == RSP_ERR) ? "Fehler" : "Warnungen",
+							GWEN_Buffer_GetStart(jd->emsg)));
+				btn1 = I18N("No");
+				btn2 = fmtStr(FB, "%s, %s", I18N("Yes"), I18N("finish"));
+			}
+			else
+			{
+				m = strdup(fmtStr(FB, "Die Übermittlung der Schlüssel ergab keinen Fehler,\n"
+						"aus den Meldungen des Bankservers sollte eine Übernahme ersichtlich sein:\n\n%s\nNeue Schlüssel / neues Medium\n"
+						"wird übernommen.", GWEN_Buffer_GetStart(jd->emsg)));
+			}
+			if(GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO, I18N("Change keys: confirm"),
+																		m, btn1, btn2, NULL, 0) == 2)
+				res = 0;
+		}
+		if(m)
+			free((char*)m);
+	}
+	DBG_NOTICE(AQHBCI_LOGDOMAIN, "%s(): %d/%d %p %p.", __FUNCTION__, jd->resp, res, u, uTmp);
+	if(uTmp)
+	{
+		DBG_INFO(AQHBCI_LOGDOMAIN, "%s: tt '%s' tn '%s' rdh %d cm %d ctx-id %d.", __FUNCTION__,
+				  AH_User_GetTokenType(uTmp), AH_User_GetTokenName(uTmp), AH_User_GetRdhType(uTmp),
+				  AH_User_GetCryptMode(uTmp), AH_User_GetTokenContextId(uTmp));
+		if(res == 0)
+		{
+			if(AB_Provider_BeginExclUseUser(pro, u))
+			{
+				DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): AB_Provider_BeginExclUseUser() failed.", __FUNCTION__);
+				res = -1;
+			}
+			else
+			{
+				AH_User_SetBankPubCryptKey(u, AH_User_GetBankPubCryptKey(uTmp));
+				AH_User_SetBankPubSignKey(u, AH_User_GetBankPubSignKey(uTmp));
+				AH_User_SetTokenType(u, AH_User_GetTokenType(uTmp));
+				AH_User_SetTokenName(u, AH_User_GetTokenName(uTmp));
+				AH_User_SetRdhType(u, AH_User_GetRdhType(uTmp));
+				AH_User_SetCryptMode(u, AH_User_GetCryptMode(uTmp));
+				AH_User_SetTokenContextId(u, AH_User_GetTokenContextId(uTmp));
+				if(AB_Provider_EndExclUseUser(pro, u, 0))
+				{
+					DBG_ERROR(AQHBCI_LOGDOMAIN, "%s(): AB_Provider_EndExclUseUser() failed.", __FUNCTION__);
+					res = -1;
+				}
+			}
+		}
+		AB_Provider_DeleteUser(pro, AB_User_GetUniqueId(uTmp));
+	}
+
+	return res;
+}
diff --git a/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys.xml b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys.xml
new file mode 100644
index 0000000..2016732
--- /dev/null
+++ b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys.xml
@@ -0,0 +1,74 @@
+<!--
+/***************************************************************************
+
+***************************************************************************
+*          Please see toplevel file COPYING for license details           *
+***************************************************************************/
+ -->
+
+<SEGs>
+	<SEGdef id="ChangeKeys"
+			code="HKSAK"
+			version="3"
+			pversion="300"
+			delimiter="+"
+			terminator="'">
+		<GROUP type="SegHead" name="head" />
+		<ELEM name="context" type="num" maxsize="1" />
+		<ELEM name="fntyp" type="num" maxsize="3" />
+		<GROUP type="SecProfile" name="secProfile" />
+		<GROUP name="keyName" type="signkeyname" />
+		<GROUP name="key" type="signkey" trustlevel="3" />
+		<GROUP name="cert" type="cert" minnum="0" />
+		<VALUES>
+			<VALUE path="head/code">%code</VALUE>
+			<VALUE path="head/version">%version</VALUE>
+			<VALUE path="context">2</VALUE>
+			<VALUE path="fntyp">112</VALUE>
+		</VALUES>
+	</SEGdef>
+</SEGs>
+
+<JOBs>
+	<JOBdef id="JobChangeKeys"
+			version="4"
+			dlg="1"
+			needbpd="0"
+	>
+		<MESSAGE name="open" >
+			<SEG type="Ident" name="ident" />
+			<SEG type="Prepare" name="prepare" version="3" />
+			<SEG type="GetKey" version="3" name="getcryptKey" />
+			<SEG type="GetKey" version="3" name="getsignKey" />
+		</MESSAGE>
+		<MESSAGE>
+			<SEG type="ChangeKeys" version="3" name="setcryptKey" />
+			<SEG type="ChangeKeys" version="3" name="setsignKey" />
+		</MESSAGE>
+		<MESSAGE name="close" >
+			<SEG type="DialogEnd" />
+		</MESSAGE>
+	</JOBdef>
+
+	<JOBdef id="JobChangeKeysA"
+			version="4"
+			dlg="1"
+			needbpd="0"
+	>
+		<MESSAGE name="open" >
+			<SEG type="Ident" name="ident" />
+			<SEG type="Prepare" name="prepare" version="3" />
+			<SEG type="GetKey" version="3" name="getcryptKey" />
+			<SEG type="GetKey" version="3" name="getsignKey" />
+		</MESSAGE>
+		<MESSAGE>
+			<SEG type="ChangeKeys" version="3" name="setcryptKey" />
+			<SEG type="ChangeKeys" version="3" name="setsignKey" />
+			<SEG type="ChangeKeys" version="3" name="setauthKey" />
+		</MESSAGE>
+		<MESSAGE name="close" >
+			<SEG type="DialogEnd" />
+		</MESSAGE>
+	</JOBdef>
+</JOBs>
+
diff --git a/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys_l.h b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys_l.h
new file mode 100644
index 0000000..8c06515
--- /dev/null
+++ b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys_l.h
@@ -0,0 +1,23 @@
+/***************************************************************************
+
+ ***************************************************************************
+ *          Please see toplevel file COPYING for license details           *
+ ***************************************************************************/
+
+
+#ifndef AH_JOBCHANGEKEYS_L_H
+#define AH_JOBCHANGEKEYS_L_H
+
+
+#include "aqhbci_l.h"
+#include "job_l.h"
+
+
+AH_JOB *AH_Job_ChangeKeys_new(AB_PROVIDER *pro, AB_USER *u, GWEN_DB_NODE *args, uint8_t *canceled);
+
+int AH_Job_ChangeKeys_finish(AB_PROVIDER *pro, AH_JOB *job, int res);
+
+
+
+#endif
+
diff --git a/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys_p.h b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys_p.h
new file mode 100644
index 0000000..8bdd729
--- /dev/null
+++ b/src/libs/plugins/backends/aqhbci/admjobs/jobchangekeys_p.h
@@ -0,0 +1,11 @@
+#ifndef AH_JOBLOCKKEYS_P_H
+#define AH_JOBLOCKKEYS_P_H
+
+
+#include "jobchangekeys_l.h"
+
+typedef struct AH_JOB_CHANGEKEYS AH_JOB_CHANGEKEYS;
+
+
+#endif
+
diff --git a/src/libs/plugins/backends/aqhbci/banking/provider.c b/src/libs/plugins/backends/aqhbci/banking/provider.c
index 75f68e7..77ffdb4 100644
--- a/src/libs/plugins/backends/aqhbci/banking/provider.c
+++ b/src/libs/plugins/backends/aqhbci/banking/provider.c
@@ -47,6 +47,7 @@
 /* admin jobs */
 #include "jobgetkeys_l.h"
 #include "jobsendkeys_l.h"
+#include "jobchangekeys_l.h"
 #include "jobgetsepainfo_l.h"
 #include "jobgetsysid_l.h"
 #include "jobgetbankinfo_l.h"
diff --git a/src/libs/plugins/backends/aqhbci/banking/provider.h b/src/libs/plugins/backends/aqhbci/banking/provider.h
index b1917af..2965753 100644
--- a/src/libs/plugins/backends/aqhbci/banking/provider.h
+++ b/src/libs/plugins/backends/aqhbci/banking/provider.h
@@ -146,6 +146,10 @@ int AH_Provider_SendUserKeys2(AB_PROVIDER *pro, AB_USER *u,
                               int withAuthKey,
                               int withProgress, int nounmount, int doLock);
 
+/**
+ */
+int AH_Provider_ChangeUserKeys(AB_PROVIDER *pro, AB_USER *u, GWEN_DB_NODE *args, int withProgress, int nounmount, int doLock);
+
 /**
  * Retrieve the SSL certificate for the given user. This is only needed for
  * PIN/TAN mode.
diff --git a/src/libs/plugins/backends/aqhbci/banking/provider_online.c b/src/libs/plugins/backends/aqhbci/banking/provider_online.c
index 823c6c9..414a99f 100644
--- a/src/libs/plugins/backends/aqhbci/banking/provider_online.c
+++ b/src/libs/plugins/backends/aqhbci/banking/provider_online.c
@@ -825,6 +825,70 @@ int AH_Provider_SendUserKeys(AB_PROVIDER *pro, AB_USER *u,
 }
 
 
+int AH_Provider_ChangeUserKeys(AB_PROVIDER *pro, AB_USER *u, GWEN_DB_NODE *args, int withProgress, int nounmount, int doLock)
+{
+	int res = 0;
+	uint8_t canceled = 0;
+	AH_JOB *job = NULL;
+	AB_IMEXPORTER_CONTEXT *ctx = NULL;
+
+	assert(u);
+
+	job = AH_Job_ChangeKeys_new(pro, u, args, &canceled);
+	if(!job)
+	{
+		res = -2;
+		if(!canceled)
+		{
+			DBG_ERROR(AQHBCI_LOGDOMAIN, "Unexplainable, 'AH_Job_ChangeKeys_new' not supported.");
+			GWEN_Gui_ProgressLog(0, GWEN_LoggerLevel_Error, I18N("Unexplainable, 'AH_Job_ChangeKeys_new' not supported."));
+			res = -1;
+		}
+		if(canceled == 2)
+			res = -1;
+	}
+
+	if(!res)
+	{
+		ctx = AB_ImExporterContext_new();
+		if(!ctx)
+		{
+			DBG_ERROR(AQHBCI_LOGDOMAIN, "Error getting ctx.");
+			res = -1;
+		}
+	}
+	if(ctx)
+	{
+		AH_OUTBOX *ob = AH_Outbox_new(pro);
+		if(!ob)
+			GWEN_Gui_ProgressLog(0, GWEN_LoggerLevel_Error, I18N("Allocate outbox failed."));
+		else
+		{
+			AH_Job_AddSigner(job, AB_User_GetUserId(u));
+			AH_Outbox_AddJob(ob, job);
+
+			GWEN_Gui_ProgressLog(0, GWEN_LoggerLevel_Info, I18N("Fetching serverkeys."));
+			res = AH_Outbox_Execute(ob, ctx, withProgress, 0, doLock);
+			AH_Outbox_free(ob);
+			if(res)
+				GWEN_Gui_ProgressLog(0, GWEN_LoggerLevel_Error, I18N("Could not execute outbox."));
+			DBG_INFO(AQHBCI_LOGDOMAIN, "res %d, status %d.", res, AH_Job_GetStatus(job));
+			if(res || (AH_Job_GetStatus(job) == AH_JobStatusError))
+				res = -1;
+		}
+		AB_ImExporterContext_free(ctx);
+	}
+
+	res = AH_Job_ChangeKeys_finish(pro, job, res);
+
+	if(job)
+		AH_Job_free(job);
+
+	AB_Banking_ClearCryptTokenList(AH_HBCI_GetBankingApi(AH_Provider_GetHbci(pro)));
+
+	return (res == -2) ? 0 : res;
+}
+
 
 int AH_Provider_GetCert(AB_PROVIDER *pro,
                         AB_USER *u,
diff --git a/src/libs/plugins/backends/aqhbci/control/Makefile.am b/src/libs/plugins/backends/aqhbci/control/Makefile.am
index e00ecbb..15d1378 100644
--- a/src/libs/plugins/backends/aqhbci/control/Makefile.am
+++ b/src/libs/plugins/backends/aqhbci/control/Makefile.am
@@ -45,6 +45,7 @@ libhbcicontrol_la_SOURCES=\
  mkpinlist.c \
  logfile.c \
  sendkeys.c \
+ changekeys.c \
  sethbciversion.c \
  setitanmode.c \
  setmaxtransfers.c \
diff --git a/src/libs/plugins/backends/aqhbci/control/changekeys.c b/src/libs/plugins/backends/aqhbci/control/changekeys.c
new file mode 100644
index 0000000..c4333df
--- /dev/null
+++ b/src/libs/plugins/backends/aqhbci/control/changekeys.c
@@ -0,0 +1,100 @@
+/***************************************************************************
+
+ ***************************************************************************
+ *          Please see toplevel file COPYING for license details           *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#include "globals_l.h"
+#include "aqhbci/banking/user.h"
+
+#include <gwenhywfar/gui.h>
+#include <gwenhywfar/text.h>
+#include <gwenhywfar/ctplugin.h>
+
+#include <stdarg.h>
+#include <unistd.h>
+
+
+int AH_Control_ChangeKeys(AB_PROVIDER *pro, GWEN_DB_NODE *dbArgs, int argc, char **argv)
+{
+	GWEN_DB_NODE *db = NULL;
+	int res = 0;
+	uint32_t uid = 0;
+	AB_USER *u = NULL;
+
+	// flags, type, name, minnum, maxnum, short option, long option, short description , long description
+	const GWEN_ARGS args[] =
+	{
+		{
+			GWEN_ARGS_FLAGS_HAS_ARGUMENT, GWEN_ArgsType_Int, "userId", 1, 1,
+									"u", "user", "user id", "user id"
+		},
+		{
+			GWEN_ARGS_FLAGS_HAS_ARGUMENT, GWEN_ArgsType_Char, "tokenType", 0, 1,
+									"t", "tokentype", "token type", "type of new crypt token (file, card)"
+		},
+		{
+			GWEN_ARGS_FLAGS_HAS_ARGUMENT, GWEN_ArgsType_Char, "tokenName", 0, 1,
+									"n", "tokenname", "token name", "name of new crypt token (filename, card-nr)"
+		},
+		{
+			GWEN_ARGS_FLAGS_HAS_ARGUMENT, GWEN_ArgsType_Int, "context", 0, 1,
+									"c", "context", "token context", "context on new token"
+		},
+		{
+			GWEN_ARGS_FLAGS_HAS_ARGUMENT, GWEN_ArgsType_Char, "cryptMode", 0, 1,
+									"m", "cryptmode", "crypt mode", "crypt mode of new token (RDH, RAH)"
+		},
+		{
+			GWEN_ARGS_FLAGS_HAS_ARGUMENT, GWEN_ArgsType_Int, "cryptType", 0, 1,
+									"T", "crypttype", "crypt type", "crypt type of new token (1 - 10, depends on crypt mode)"
+		},
+		{
+			GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, GWEN_ArgsType_Int, "help",  0, 0,
+									"h", "help", "Show this help screen", "Show this help screen"
+		}
+	};
+
+	db = GWEN_DB_GetGroup(dbArgs, GWEN_DB_FLAGS_DEFAULT, "local");
+	if(!db || ((res = GWEN_Args_Check(argc, argv, 1, 0, args, db)) == GWEN_ARGS_RESULT_ERROR))
+	{
+		fprintf(stderr, "ERROR: Could not parse arguments\n");
+		return 1;
+	}
+
+	if(res == GWEN_ARGS_RESULT_HELP)
+	{
+		GWEN_BUFFER *b = GWEN_Buffer_new(0, 1024, 0, 1);
+		if(GWEN_Args_Usage(args, b, GWEN_ArgsOutType_Txt))
+		{
+			fprintf(stderr, "ERROR: Could not create help string\n");
+			return 1;
+		}
+		// TODO: help text default args
+		fprintf(stdout, "%s\n", GWEN_Buffer_GetStart(b));
+		GWEN_Buffer_free(b);
+		return 0;
+	}
+
+	if((uid = (uint32_t)GWEN_DB_GetIntValue(db, "userId", 0, 0)) == 0)
+	{
+		fprintf(stderr, "ERROR: Invalid or missing unique user id\n");
+		return 1;
+	}
+
+	if((AB_Provider_HasUser(pro, uid) < 0) || (AB_Provider_GetUser(pro, uid, 1, 1, &u) < 0) || !u)
+	{
+		fprintf(stderr, "ERROR: could not find user with id %ld.", (long)uid);
+		return -1;
+	}
+
+	res = AH_Provider_ChangeUserKeys(pro, u, db, 1, 0, 0);
+
+	AB_User_free(u);
+	return res;
+}
diff --git a/src/libs/plugins/backends/aqhbci/control/globals_l.h b/src/libs/plugins/backends/aqhbci/control/globals_l.h
index ee92f9b..479f857 100644
--- a/src/libs/plugins/backends/aqhbci/control/globals_l.h
+++ b/src/libs/plugins/backends/aqhbci/control/globals_l.h
@@ -91,6 +91,11 @@ int AH_Control_SendKeys(AB_PROVIDER *pro,
                         int argc,
                         char **argv);
 
+int AH_Control_ChangeKeys(AB_PROVIDER *pro,
+						GWEN_DB_NODE *dbArgs,
+						int argc,
+						char **argv);
+
 int AH_Control_ListUsers(AB_PROVIDER *pro,
                          GWEN_DB_NODE *dbArgs,
                          int argc,
diff --git a/src/libs/plugins/backends/aqhbci/control/main.c b/src/libs/plugins/backends/aqhbci/control/main.c
index 48a6e1d..49671e2 100644
--- a/src/libs/plugins/backends/aqhbci/control/main.c
+++ b/src/libs/plugins/backends/aqhbci/control/main.c
@@ -76,6 +76,9 @@ static void showUsage(const char *prgName)
   GWEN_Buffer_AppendString(ubuf,
                            I18N("  sendkeys:\n"
                                 "    Send the user keys to the bank.\n\n"));
+  GWEN_Buffer_AppendString(ubuf,
+						   I18N("  changekeys:\n"
+								"    Change user keys (and media).\n\n"));
   GWEN_Buffer_AppendString(ubuf,
                            I18N("  getaccounts:\n"
                                 "    Requests account list for a "
@@ -195,6 +198,9 @@ int AH_Control(AB_PROVIDER *pro, int argc, char **argv)
   else if (strcasecmp(cmd, "sendkeys")==0) {
     rv=AH_Control_SendKeys(pro, db, argc, argv);
   }
+  else if (strcasecmp(cmd, "changekeys")==0) {
+	rv=AH_Control_ChangeKeys(pro, db, argc, argv);
+  }
   else if (strcasecmp(cmd, "listusers")==0) {
     rv=AH_Control_ListUsers(pro, db, argc, argv);
   }
